diff options
Diffstat (limited to 'desktop')
52 files changed, 8818 insertions, 5829 deletions
diff --git a/desktop/Makefile b/desktop/Makefile index ffd932177..5e190275d 100644 --- a/desktop/Makefile +++ b/desktop/Makefile @@ -2,8 +2,8 @@ S_DESKTOP := cookie_manager.c knockout.c hotlist.c mouse.c \ plot_style.c print.c search.c searchweb.c scrollbar.c \ - sslcert_viewer.c textarea.c version.c system_colour.c \ - local_history.c global_history.c treeview.c + textarea.c version.c system_colour.c \ + local_history.c global_history.c treeview.c page-info.c S_DESKTOP := $(addprefix desktop/,$(S_DESKTOP)) @@ -12,7 +12,8 @@ desktop/version.c: testament $(OBJROOT)/testament.h # S_BROWSER are sources related to full browsers but are common # between RISC OS, GTK, BeOS and AmigaOS builds -S_BROWSER := browser.c browser_history.c download.c frames.c netsurf.c \ +S_BROWSER := bitmap.c browser.c browser_window.c browser_history.c \ + download.c frames.c netsurf.c cw_helper.c \ save_complete.c save_text.c selection.c textinput.c gui_factory.c \ save_pdf.c font_haru.c diff --git a/desktop/bitmap.c b/desktop/bitmap.c new file mode 100644 index 000000000..0602773ca --- /dev/null +++ b/desktop/bitmap.c @@ -0,0 +1,338 @@ +/* + * Copyright 2022 Michael Drake <tlsa@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** \file + * Internal core bitmap interface. + */ + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#include "utils/log.h" +#include "utils/errors.h" + +#include "desktop/bitmap.h" +#include "desktop/gui_internal.h" + +/** The client bitmap format. */ +bitmap_fmt_t bitmap_fmt; + +/** The client bitmap colour channel layout. */ +struct bitmap_colour_layout bitmap_layout = { + .r = 0, + .g = 1, + .b = 2, + .a = 3, +}; + +/** + * Get the colour layout for the given bitmap format. + * + * \param[in] fmt Pixel format to get channel layout for, + * \return channel layout structure. + */ +static struct bitmap_colour_layout bitmap__get_colour_layout( + const bitmap_fmt_t *fmt) +{ + switch (fmt->layout) { + default: + /* Fall through. */ + case BITMAP_LAYOUT_R8G8B8A8: + return (struct bitmap_colour_layout) { + .r = 0, + .g = 1, + .b = 2, + .a = 3, + }; + + case BITMAP_LAYOUT_B8G8R8A8: + return (struct bitmap_colour_layout) { + .b = 0, + .g = 1, + .r = 2, + .a = 3, + }; + + case BITMAP_LAYOUT_A8R8G8B8: + return (struct bitmap_colour_layout) { + .a = 0, + .r = 1, + .g = 2, + .b = 3, + }; + + case BITMAP_LAYOUT_A8B8G8R8: + return (struct bitmap_colour_layout) { + .a = 0, + .b = 1, + .g = 2, + .r = 3, + }; + } +} + +/** + * Get string for given pixel layout. + * + * \param[in] layout The pixel layout to get string for, + * \return String for given layout. + */ +static const char *bitmap__layout_to_str(enum bitmap_layout layout) +{ + const char *const str[] = { + [BITMAP_LAYOUT_R8G8B8A8] = "Byte-wise RGBA", + [BITMAP_LAYOUT_B8G8R8A8] = "Byte-wise BGRA", + [BITMAP_LAYOUT_A8R8G8B8] = "Byte-wise ARGB", + [BITMAP_LAYOUT_A8B8G8R8] = "Byte-wise ABGR", + [BITMAP_LAYOUT_RGBA8888] = "0xRRGGBBAA (native endian)", + [BITMAP_LAYOUT_BGRA8888] = "0xBBGGRRAA (native endian)", + [BITMAP_LAYOUT_ARGB8888] = "0xAARRGGBB (native endian)", + [BITMAP_LAYOUT_ABGR8888] = "0xAABBGGRR (native endian)", + }; + + if ((size_t)layout >= (sizeof(str)) / sizeof(*str) || + str[layout] == NULL) { + return "Unknown"; + } + + return str[layout]; +} + +/* Exported function, documented in include/netsurf/bitmap.h */ +void bitmap_set_format(const bitmap_fmt_t *bitmap_format) +{ + bitmap_fmt = *bitmap_format; + + NSLOG(netsurf, INFO, "Setting core bitmap format to: %s%s", + bitmap__layout_to_str(bitmap_format->layout), + bitmap_format->pma ? " pre multiplied alpha" : ""); + + bitmap_fmt.layout = bitmap_sanitise_bitmap_layout(bitmap_fmt.layout); + + if (bitmap_format->layout != bitmap_fmt.layout) { + NSLOG(netsurf, INFO, "Sanitised layout to: %s", + bitmap__layout_to_str(bitmap_fmt.layout)); + } + + bitmap_layout = bitmap__get_colour_layout(&bitmap_fmt); +} + +/** + * Swap colour component order. + * + * \param[in] width Bitmap width in pixels. + * \param[in] height Bitmap height in pixels. + * \param[in] buffer Pixel buffer. + * \param[in] rowstride Pixel buffer row stride in bytes. + * \param[in] to Pixel layout to convert to. + * \param[in] from Pixel layout to convert from. + */ +static inline void bitmap__format_convert( + int width, + int height, + uint8_t *buffer, + size_t rowstride, + struct bitmap_colour_layout to, + struct bitmap_colour_layout from) +{ + /* Just swapping the components around */ + for (int y = 0; y < height; y++) { + uint8_t *row = buffer; + + for (int x = 0; x < width; x++) { + const uint32_t px = *((uint32_t *)(void *) row); + + row[to.r] = ((const uint8_t *) &px)[from.r]; + row[to.g] = ((const uint8_t *) &px)[from.g]; + row[to.b] = ((const uint8_t *) &px)[from.b]; + row[to.a] = ((const uint8_t *) &px)[from.a]; + + row += sizeof(uint32_t); + } + + buffer += rowstride; + } +} + +/** + * Convert plain alpha to premultiplied alpha. + * + * \param[in] width Bitmap width in pixels. + * \param[in] height Bitmap height in pixels. + * \param[in] buffer Pixel buffer. + * \param[in] rowstride Pixel buffer row stride in bytes. + * \param[in] to Pixel layout to convert to. + * \param[in] from Pixel layout to convert from. + */ +static inline void bitmap__format_convert_to_pma( + int width, + int height, + uint8_t *buffer, + size_t rowstride, + struct bitmap_colour_layout to, + struct bitmap_colour_layout from) +{ + for (int y = 0; y < height; y++) { + uint8_t *row = buffer; + + for (int x = 0; x < width; x++) { + const uint32_t px = *((uint32_t *)(void *) row); + uint32_t a, r, g, b; + + r = ((const uint8_t *) &px)[from.r]; + g = ((const uint8_t *) &px)[from.g]; + b = ((const uint8_t *) &px)[from.b]; + a = ((const uint8_t *) &px)[from.a]; + + if (a != 0) { + r = ((r * (a + 1)) >> 8) & 0xff; + g = ((g * (a + 1)) >> 8) & 0xff; + b = ((b * (a + 1)) >> 8) & 0xff; + } else { + r = g = b = 0; + } + + row[to.r] = r; + row[to.g] = g; + row[to.b] = b; + row[to.a] = a; + + row += sizeof(uint32_t); + } + + buffer += rowstride; + } +} + +/** + * Convert from premultiplied alpha to plain alpha. + * + * \param[in] width Bitmap width in pixels. + * \param[in] height Bitmap height in pixels. + * \param[in] buffer Pixel buffer. + * \param[in] rowstride Pixel buffer row stride in bytes. + * \param[in] to Pixel layout to convert to. + * \param[in] from Pixel layout to convert from. + */ +static inline void bitmap__format_convert_from_pma( + int width, + int height, + uint8_t *buffer, + size_t rowstride, + struct bitmap_colour_layout to, + struct bitmap_colour_layout from) +{ + for (int y = 0; y < height; y++) { + uint8_t *row = buffer; + + for (int x = 0; x < width; x++) { + const uint32_t px = *((uint32_t *)(void *) row); + uint32_t a, r, g, b; + + r = ((const uint8_t *) &px)[from.r]; + g = ((const uint8_t *) &px)[from.g]; + b = ((const uint8_t *) &px)[from.b]; + a = ((const uint8_t *) &px)[from.a]; + + if (a != 0) { + r = (r << 8) / a; + g = (g << 8) / a; + b = (b << 8) / a; + + r = (r > 255) ? 255 : r; + g = (g > 255) ? 255 : g; + b = (b > 255) ? 255 : b; + } else { + r = g = b = 0; + } + + row[to.r] = r; + row[to.g] = g; + row[to.b] = b; + row[to.a] = a; + + row += sizeof(uint32_t); + } + + buffer += rowstride; + } +} + +/* Exported function, documented in desktop/bitmap.h */ +void bitmap_format_convert(void *bitmap, + const bitmap_fmt_t *fmt_from, + const bitmap_fmt_t *fmt_to) +{ + int width = guit->bitmap->get_width(bitmap); + int height = guit->bitmap->get_height(bitmap); + bool opaque = guit->bitmap->get_opaque(bitmap); + uint8_t *buffer = guit->bitmap->get_buffer(bitmap); + size_t rowstride = guit->bitmap->get_rowstride(bitmap); + struct bitmap_colour_layout to = bitmap__get_colour_layout(fmt_to); + struct bitmap_colour_layout from = bitmap__get_colour_layout(fmt_from); + + NSLOG(netsurf, DEEPDEBUG, "%p: format conversion (%u%s --> %u%s)", + bitmap, + fmt_from->layout, fmt_from->pma ? " pma" : "", + fmt_to->layout, fmt_to->pma ? " pma" : ""); + + if (fmt_from->pma == fmt_to->pma) { + /* Just component order to switch. */ + bitmap__format_convert( + width, height, buffer, + rowstride, to, from); + + } else if (opaque == false) { + /* Need to do conversion to/from premultiplied alpha. */ + if (fmt_to->pma) { + bitmap__format_convert_to_pma( + width, height, buffer, + rowstride, to, from); + } else { + bitmap__format_convert_from_pma( + width, height, buffer, + rowstride, to, from); + } + } +} + +/* Exported function, documented in desktop/bitmap.h */ +bool bitmap_test_opaque(void *bitmap) +{ + int width = guit->bitmap->get_width(bitmap); + int height = guit->bitmap->get_height(bitmap); + size_t rowstride = guit->bitmap->get_rowstride(bitmap); + const uint8_t *buffer = guit->bitmap->get_buffer(bitmap); + + width *= sizeof(uint32_t); + + for (int y = 0; y < height; y++) { + const uint8_t *row = buffer; + + for (int x = bitmap_layout.a; x < width; x += 4) { + if (row[x] != 0xff) { + return false; + } + } + + buffer += rowstride; + } + + return true; +} diff --git a/desktop/bitmap.h b/desktop/bitmap.h new file mode 100644 index 000000000..51ce2c908 --- /dev/null +++ b/desktop/bitmap.h @@ -0,0 +1,147 @@ +/* + * Copyright 2022 Michael Drake <tlsa@nesturf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** \file + * Internal core bitmap interface. + */ + +#ifndef _NETSURF_DESKTOP_BITMAP_H_ +#define _NETSURF_DESKTOP_BITMAP_H_ + +#include <nsutils/endian.h> + +#include "netsurf/types.h" +#include "netsurf/bitmap.h" + +/** Pixel format: colour component order. */ +struct bitmap_colour_layout { + uint8_t r; /**< Byte offset within pixel to red component. */ + uint8_t g; /**< Byte offset within pixel to green component. */ + uint8_t b; /**< Byte offset within pixel to blue component. */ + uint8_t a; /**< Byte offset within pixel to alpha component. */ +}; + +/** The client bitmap format. */ +extern bitmap_fmt_t bitmap_fmt; + +/** The client bitmap colour channel layout. */ +extern struct bitmap_colour_layout bitmap_layout; + +/** + * Convert a bitmap pixel to a NetSurf colour (0xAARRGGBB). + * + * The bitmap must be in the client format. + * + * \param[in] Pointer to a pixel in the bitmap's pixel data. + * \return The corresponding NetSurf colour. + */ +static inline colour bitmap_pixel_to_colour(const uint8_t *pixel) +{ + return (pixel[bitmap_layout.r] << 0) | + (pixel[bitmap_layout.g] << 8) | + (pixel[bitmap_layout.b] << 16) | + (pixel[bitmap_layout.a] << 24); +} + +/** + * Sanitise bitmap pixel component layout. + * + * Map endian-dependant layouts to byte-wise layout for the host. + * + * \param[in] layout Layout to convert. + * \return sanitised layout. + */ +static inline enum bitmap_layout bitmap_sanitise_bitmap_layout( + enum bitmap_layout layout) +{ + bool le = endian_host_is_le(); + + switch (layout) { + case BITMAP_LAYOUT_RGBA8888: + layout = (le) ? BITMAP_LAYOUT_A8B8G8R8 + : BITMAP_LAYOUT_R8G8B8A8; + break; + case BITMAP_LAYOUT_BGRA8888: + layout = (le) ? BITMAP_LAYOUT_A8R8G8B8 + : BITMAP_LAYOUT_B8G8R8A8; + break; + case BITMAP_LAYOUT_ARGB8888: + layout = (le) ? BITMAP_LAYOUT_B8G8R8A8 + : BITMAP_LAYOUT_A8R8G8B8; + break; + case BITMAP_LAYOUT_ABGR8888: + layout = (le) ? BITMAP_LAYOUT_R8G8B8A8 + : BITMAP_LAYOUT_A8B8G8R8; + break; + default: + break; + } + + return layout; +} + +/** + * Convert bitmap from one format to another. + * + * Note that both formats should be sanitised. + * + * \param[in] bitmap The bitmap to convert. + * \param[in] from The current bitmap format specifier. + * \param[in] to The bitmap format to convert to. + */ +void bitmap_format_convert(void *bitmap, + const bitmap_fmt_t *from, + const bitmap_fmt_t *to); + +/** + * Convert a bitmap to the client bitmap format. + * + * \param[in] bitmap The bitmap to convert. + * \param[in] current_fmt The current bitmap format specifier. + */ +static inline void bitmap_format_to_client( + void *bitmap, + const bitmap_fmt_t *current_fmt) +{ + bitmap_fmt_t from = *current_fmt; + + from.layout = bitmap_sanitise_bitmap_layout(from.layout); + if (from.layout != bitmap_fmt.layout || from.pma != bitmap_fmt.pma) { + bitmap_format_convert(bitmap, &from, &bitmap_fmt); + } +} + +/** + * Convert a bitmap to the client bitmap format. + * + * \param[in] bitmap The bitmap to convert. + * \param[in] target_fmt The target bitmap format specifier. + */ +static inline void bitmap_format_from_client( + void *bitmap, + const bitmap_fmt_t *target_fmt) +{ + bitmap_fmt_t to = *target_fmt; + + to.layout = bitmap_sanitise_bitmap_layout(to.layout); + if (to.layout != bitmap_fmt.layout || to.pma != bitmap_fmt.pma) { + bitmap_format_convert(bitmap, &bitmap_fmt, &to); + } +} + +#endif diff --git a/desktop/browser.c b/desktop/browser.c index 19cfebb99..6968bf21b 100644 --- a/desktop/browser.c +++ b/desktop/browser.c @@ -1,11 +1,5 @@ /* - * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> - * Copyright 2006 James Bursa <bursa@users.sourceforge.net> - * Copyright 2004 Andrew Timmins <atimmins@blueyonder.co.uk> - * Copyright 2004 John Tytgat <joty@netsurf-browser.org> - * Copyright 2006 Richard Wilson <info@tinct.net> - * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> - * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> + * Copyright 2019 Vincent Sanders <vince@netsurf-browser.org> * * This file is part of NetSurf, http://www.netsurf-browser.org/ * @@ -25,3349 +19,29 @@ /** * \file * - * Browser window creation and manipulation implementation. + * Browser core functionality */ -#include "utils/config.h" - -#include <assert.h> -#include <limits.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> -#include <string.h> -#include <strings.h> -#include <math.h> -#include <nsutils/time.h> - -#include "utils/corestrings.h" +#include "utils/errors.h" #include "utils/log.h" -#include "utils/messages.h" -#include "utils/nsurl.h" #include "utils/utils.h" -#include "utils/utf8.h" -#include "utils/nsoption.h" -#include "netsurf/misc.h" -#include "netsurf/window.h" -#include "netsurf/content.h" -#include "netsurf/plotters.h" -#include "content/content_debug.h" -#include "content/fetch.h" -#include "content/hlcache.h" -#include "content/urldb.h" +#include "netsurf/browser.h" #include "css/utils.h" -#include "render/form_internal.h" -#include "render/html.h" -#include "render/box.h" -#include "javascript/js.h" - -#include "desktop/browser_history.h" -#include "desktop/browser_private.h" -#include "desktop/download.h" -#include "desktop/frames.h" -#include "desktop/global_history.h" -#include "desktop/hotlist.h" -#include "desktop/knockout.h" -#include "desktop/scrollbar.h" -#include "desktop/selection.h" -#include "desktop/theme.h" -#include "desktop/gui_internal.h" -#include "desktop/textinput.h" - - -/** maximum frame depth */ -#define FRAME_DEPTH 8 - - -/** - * Get position of scrollbar widget within browser window. - * - * \param bw The browser window - * \param horizontal Whether to get position of horizontal scrollbar - * \param x Updated to x-coord of top left of scrollbar widget - * \param y Updated to y-coord of top left of scrollbar widget - */ -static inline void browser_window_get_scrollbar_pos(struct browser_window *bw, - bool horizontal, int *x, int *y) -{ - if (horizontal) { - *x = 0; - *y = bw->height - SCROLLBAR_WIDTH; - } else { - *x = bw->width - SCROLLBAR_WIDTH; - *y = 0; - } -} - - -/** - * Get browser window scrollbar widget length - * - * \param bw The browser window - * \param horizontal Whether to get length of horizontal scrollbar - * \return the scrollbar's length - */ -static inline int browser_window_get_scrollbar_len(struct browser_window *bw, - bool horizontal) -{ - if (horizontal) - return bw->width - (bw->scroll_y != NULL ? SCROLLBAR_WIDTH : 0); - else - return bw->height; -} - - -/* exported interface, documented in browser.h */ -nserror -browser_window_get_name(struct browser_window *bw, const char **out_name) -{ - assert(bw != NULL); - - *out_name = bw->name; - - return NSERROR_OK; -} - - -/* exported interface, documented in browser.h */ -nserror -browser_window_set_name(struct browser_window *bw, const char *name) -{ - char *nname = NULL; - - assert(bw != NULL); - - if (name != NULL) { - nname = strdup(name); - if (nname == NULL) { - return NSERROR_NOMEM; - } - } - - if (bw->name != NULL) { - free(bw->name); - } - - bw->name = nname; - - return NSERROR_OK; -} - - -/* exported interface, documented in browser.h */ -bool -browser_window_redraw(struct browser_window *bw, - int x, int y, - const struct rect *clip, - const struct redraw_context *ctx) -{ - struct redraw_context new_ctx = *ctx; - int width = 0; - int height = 0; - bool plot_ok = true; - content_type content_type; - struct content_redraw_data data; - struct rect content_clip; - nserror res; - - if (bw == NULL) { - NSLOG(netsurf, INFO, "NULL browser window"); - return false; - } - - if ((bw->current_content == NULL) && - (bw->children == NULL)) { - /* Browser window has no content, render blank fill */ - ctx->plot->clip(ctx, clip); - return (ctx->plot->rectangle(ctx, plot_style_fill_white, clip) == NSERROR_OK); - } - - /* Browser window has content OR children (frames) */ - if ((bw->window != NULL) && - (ctx->plot->option_knockout)) { - /* Root browser window: start knockout */ - knockout_plot_start(ctx, &new_ctx); - } - - new_ctx.plot->clip(ctx, clip); - - /* Handle redraw of any browser window children */ - if (bw->children) { - struct browser_window *child; - int cur_child; - int children = bw->rows * bw->cols; - - if (bw->window != NULL) { - /* Root browser window; start with blank fill */ - plot_ok &= (new_ctx.plot->rectangle(ctx, - plot_style_fill_white, - clip) == NSERROR_OK); - } - - /* Loop through all children of bw */ - for (cur_child = 0; cur_child < children; cur_child++) { - /* Set current child */ - child = &bw->children[cur_child]; - - /* Get frame edge box in global coordinates */ - content_clip.x0 = (x + child->x) * child->scale; - content_clip.y0 = (y + child->y) * child->scale; - content_clip.x1 = content_clip.x0 + - child->width * child->scale; - content_clip.y1 = content_clip.y0 + - child->height * child->scale; - - /* Intersect it with clip rectangle */ - if (content_clip.x0 < clip->x0) - content_clip.x0 = clip->x0; - if (content_clip.y0 < clip->y0) - content_clip.y0 = clip->y0; - if (clip->x1 < content_clip.x1) - content_clip.x1 = clip->x1; - if (clip->y1 < content_clip.y1) - content_clip.y1 = clip->y1; - - /* Skip this frame if it lies outside clip rectangle */ - if (content_clip.x0 >= content_clip.x1 || - content_clip.y0 >= content_clip.y1) - continue; - - /* Redraw frame */ - plot_ok &= browser_window_redraw(child, - x + child->x, y + child->y, - &content_clip, &new_ctx); - } - - /* Nothing else to redraw for browser windows with children; - * cleanup and return - */ - if (bw->window != NULL && ctx->plot->option_knockout) { - /* Root browser window: knockout end */ - knockout_plot_end(ctx); - } - - return plot_ok; - } - - /* Handle browser windows with content to redraw */ - - content_type = content_get_type(bw->current_content); - if (content_type != CONTENT_HTML && content_type != CONTENT_TEXTPLAIN) { - /* Set render area according to scale */ - width = content_get_width(bw->current_content) * bw->scale; - height = content_get_height(bw->current_content) * bw->scale; - - /* Non-HTML may not fill viewport to extents, so plot white - * background fill */ - plot_ok &= (new_ctx.plot->rectangle(&new_ctx, - plot_style_fill_white, - clip) == NSERROR_OK); - } - - /* Set up content redraw data */ - data.x = x - scrollbar_get_offset(bw->scroll_x); - data.y = y - scrollbar_get_offset(bw->scroll_y); - data.width = width; - data.height = height; - - data.background_colour = 0xFFFFFF; - data.scale = bw->scale; - data.repeat_x = false; - data.repeat_y = false; - - content_clip = *clip; - - if (!bw->window) { - int x0 = x * bw->scale; - int y0 = y * bw->scale; - int x1 = (x + bw->width - ((bw->scroll_y != NULL) ? - SCROLLBAR_WIDTH : 0)) * bw->scale; - int y1 = (y + bw->height - ((bw->scroll_x != NULL) ? - SCROLLBAR_WIDTH : 0)) * bw->scale; - - if (content_clip.x0 < x0) content_clip.x0 = x0; - if (content_clip.y0 < y0) content_clip.y0 = y0; - if (x1 < content_clip.x1) content_clip.x1 = x1; - if (y1 < content_clip.y1) content_clip.y1 = y1; - } - - /* Render the content */ - plot_ok &= content_redraw(bw->current_content, &data, - &content_clip, &new_ctx); - - /* Back to full clip rect */ - new_ctx.plot->clip(&new_ctx, clip); - - if (!bw->window) { - /* Render scrollbars */ - int off_x, off_y; - if (bw->scroll_x != NULL) { - browser_window_get_scrollbar_pos(bw, true, - &off_x, &off_y); - res = scrollbar_redraw(bw->scroll_x, - x + off_x, y + off_y, clip, - bw->scale, &new_ctx); - if (res != NSERROR_OK) { - plot_ok = false; - } - } - if (bw->scroll_y != NULL) { - browser_window_get_scrollbar_pos(bw, false, - &off_x, &off_y); - res = scrollbar_redraw(bw->scroll_y, - x + off_x, y + off_y, clip, - bw->scale, &new_ctx); - if (res != NSERROR_OK) { - plot_ok = false; - } - } - } - - if (bw->window != NULL && ctx->plot->option_knockout) { - /* Root browser window: end knockout */ - knockout_plot_end(ctx); - } - - return plot_ok; -} - - -/* exported interface, documented in browser.h */ -bool browser_window_redraw_ready(struct browser_window *bw) -{ - if (bw == NULL) { - NSLOG(netsurf, INFO, "NULL browser window"); - return false; - } else if (bw->current_content != NULL) { - /* Can't render locked contents */ - return !content_is_locked(bw->current_content); - } - - return true; -} - - -/* exported interface, documented in browser_private.h */ -void browser_window_update_extent(struct browser_window *bw) -{ - if (bw->window != NULL) - /* Front end window */ - guit->window->update_extent(bw->window); - else - /* Core-managed browser window */ - browser_window_handle_scrollbars(bw); -} - - -/* exported interface, documented in browser.h */ -void -browser_window_get_position(struct browser_window *bw, - bool root, - int *pos_x, - int *pos_y) -{ - *pos_x = 0; - *pos_y = 0; - - assert(bw != NULL); - - while (bw) { - switch (bw->browser_window_type) { - - case BROWSER_WINDOW_FRAMESET: - *pos_x += bw->x * bw->scale; - *pos_y += bw->y * bw->scale; - break; - - case BROWSER_WINDOW_NORMAL: - /* There is no offset to the root browser window */ - break; - - case BROWSER_WINDOW_FRAME: - /* Iframe and Frame handling is identical; - * fall though */ - case BROWSER_WINDOW_IFRAME: - *pos_x += (bw->x - scrollbar_get_offset(bw->scroll_x)) * - bw->scale; - *pos_y += (bw->y - scrollbar_get_offset(bw->scroll_y)) * - bw->scale; - break; - } - - bw = bw->parent; - - if (!root) { - /* return if we just wanted the position in the parent - * browser window. */ - return; - } - } -} - - -/* exported interface, documented in browser.h */ -void browser_window_set_position(struct browser_window *bw, int x, int y) -{ - assert(bw != NULL); - - if (bw->window == NULL) { - /* Core managed browser window */ - bw->x = x; - bw->y = y; - } else { - NSLOG(netsurf, INFO, - "Asked to set position of front end window."); - assert(0); - } -} - -/* exported interface, documented in browser.h */ -void -browser_window_set_drag_type(struct browser_window *bw, - browser_drag_type type, - const struct rect *rect) -{ - struct browser_window *top_bw = browser_window_get_root(bw); - gui_drag_type gtype; - - bw->drag.type = type; - - if (type == DRAGGING_NONE) { - top_bw->drag.window = NULL; - } else { - top_bw->drag.window = bw; - - switch (type) { - case DRAGGING_SELECTION: - /** \todo tell front end */ - return; - case DRAGGING_SCR_X: - case DRAGGING_SCR_Y: - case DRAGGING_CONTENT_SCROLLBAR: - gtype = GDRAGGING_SCROLLBAR; - break; - default: - gtype = GDRAGGING_OTHER; - break; - } - - guit->window->drag_start(top_bw->window, gtype, rect); - } -} - -/* exported interface, documented in browser.h */ -browser_drag_type browser_window_get_drag_type(struct browser_window *bw) -{ - return bw->drag.type; -} - -/* exported interface, documented in browser.h */ -struct browser_window * browser_window_get_root(struct browser_window *bw) -{ - while (bw && bw->parent) { - bw = bw->parent; - } - return bw; -} - -/* exported interface, documented in browser.h */ -browser_editor_flags browser_window_get_editor_flags(struct browser_window *bw) -{ - browser_editor_flags ed_flags = BW_EDITOR_NONE; - assert(bw->window); - assert(bw->parent == NULL); - - if (bw->selection.bw != NULL) { - ed_flags |= BW_EDITOR_CAN_COPY; - - if (!bw->selection.read_only) - ed_flags |= BW_EDITOR_CAN_CUT; - } - - if (bw->can_edit) - ed_flags |= BW_EDITOR_CAN_PASTE; - - return ed_flags; -} - -/* exported interface, documented in browser.h */ -bool browser_window_can_select(struct browser_window *bw) -{ - if (bw == NULL || bw->current_content == NULL) - return false; - - /* TODO: We shouldn't have to know about specific content types - * here. There should be a content_is_selectable() call. */ - if (content_get_type(bw->current_content) != CONTENT_HTML && - content_get_type(bw->current_content) != - CONTENT_TEXTPLAIN) - return false; - - return true; -} - -/* exported interface, documented in browser.h */ -char * browser_window_get_selection(struct browser_window *bw) -{ - assert(bw->window); - assert(bw->parent == NULL); - - if (bw->selection.bw == NULL || - bw->selection.bw->current_content == NULL) - return NULL; - - return content_get_selection(bw->selection.bw->current_content); -} - -/* exported interface, documented in netsurf/browser_window.h */ -bool browser_window_can_search(struct browser_window *bw) -{ - if (bw == NULL || bw->current_content == NULL) - return false; - - /** \todo We shouldn't have to know about specific content - * types here. There should be a content_is_searchable() call. - */ - if ((content_get_type(bw->current_content) != CONTENT_HTML) && - (content_get_type(bw->current_content) != CONTENT_TEXTPLAIN)) { - return false; - } - - return true; -} - - -/* exported interface, documented in netsurf/browser_window.h */ -bool browser_window_is_frameset(struct browser_window *bw) -{ - return (bw->children != NULL); -} - - -/* exported interface, documented in netsurf/browser_window.h */ -nserror browser_window_get_scrollbar_type(struct browser_window *bw, - browser_scrolling *h, browser_scrolling *v) -{ - *h = bw->scrolling; - *v = bw->scrolling; - - return NSERROR_OK; -} - -/** - * Set or remove a selection. - * - * \param bw browser window with selection - * \param selection true if bw has a selection, false if removing selection - * \param read_only true iff selection is read only (e.g. can't cut it) - */ -static void browser_window_set_selection(struct browser_window *bw, - bool selection, bool read_only) -{ - struct browser_window *top; - - assert(bw != NULL); - - top = browser_window_get_root(bw); - - assert(top != NULL); - - if (bw != top->selection.bw && top->selection.bw != NULL && - top->selection.bw->current_content != NULL) { - /* clear old selection */ - content_clear_selection(top->selection.bw->current_content); - } - - if (selection) { - top->selection.bw = bw; - } else { - top->selection.bw = NULL; - } - - top->selection.read_only = read_only; -} - - -/** - * Set the scroll position of a browser window. - * - * scrolls the viewport to ensure the specified rectangle of the - * content is shown. - * - * \param bw window to scroll - * \param rect The rectangle to ensure is shown. - * \return NSERROR_OK on success or apropriate error code. - */ -static nserror -browser_window_set_scroll(struct browser_window *bw, - const struct rect *rect) -{ - if (bw->window != NULL) { - return guit->window->set_scroll(bw->window, rect); - } - - if (bw->scroll_x != NULL) { - scrollbar_set(bw->scroll_x, rect->x0, false); - } - if (bw->scroll_y != NULL) { - scrollbar_set(bw->scroll_y, rect->y0, false); - } - - return NSERROR_OK; -} - -/** - * Internal helper for getting the positional features - * - * \param[in] bw browser window to examine. - * \param[in] x x-coordinate of point of interest - * \param[in] y y-coordinate of point of interest - * \param[out] data Feature structure to update. - * \return NSERROR_OK or appropriate error code on faliure. - */ -static nserror -browser_window__get_contextual_content(struct browser_window *bw, - int x, int y, struct browser_window_features *data) -{ - nserror ret = NSERROR_OK; - - /* Handle (i)frame scroll offset (core-managed browser windows only) */ - x += scrollbar_get_offset(bw->scroll_x); - y += scrollbar_get_offset(bw->scroll_y); - - if (bw->children) { - /* Browser window has children, so pass request on to - * appropriate child. - */ - struct browser_window *bwc; - int cur_child; - int children = bw->rows * bw->cols; - - /* Loop through all children of bw */ - for (cur_child = 0; cur_child < children; cur_child++) { - /* Set current child */ - bwc = &bw->children[cur_child]; - - /* Skip this frame if (x, y) coord lies outside */ - if ((x < bwc->x) || - (bwc->x + bwc->width < x) || - (y < bwc->y) || - (bwc->y + bwc->height < y)) { - continue; - } - - /* Pass request into this child */ - return browser_window__get_contextual_content(bwc, - (x - bwc->x), (y - bwc->y), data); - } - - /* Coordinate not contained by any frame */ - - } else if (bw->current_content != NULL) { - /* Pass request to content */ - ret = content_get_contextual_content(bw->current_content, - x, y, data); - data->main = bw->current_content; - } - - return ret; -} - -/* exported interface, documented in browser.h */ -nserror browser_window_get_features(struct browser_window *bw, - int x, int y, struct browser_window_features *data) -{ - /* clear the features structure to empty values */ - data->link = NULL; - data->object = NULL; - data->main = NULL; - data->form_features = CTX_FORM_NONE; - - return browser_window__get_contextual_content(bw, x, y, data); -} - -/* exported interface, documented in browser.h */ -bool browser_window_scroll_at_point(struct browser_window *bw, - int x, int y, int scrx, int scry) -{ - bool handled_scroll = false; - assert(bw != NULL); - - /* Handle (i)frame scroll offset (core-managed browser windows only) */ - x += scrollbar_get_offset(bw->scroll_x); - y += scrollbar_get_offset(bw->scroll_y); - - if (bw->children) { - /* Browser window has children, so pass request on to - * appropriate child */ - struct browser_window *bwc; - int cur_child; - int children = bw->rows * bw->cols; - - /* Loop through all children of bw */ - for (cur_child = 0; cur_child < children; cur_child++) { - /* Set current child */ - bwc = &bw->children[cur_child]; - - /* Skip this frame if (x, y) coord lies outside */ - if (x < bwc->x || bwc->x + bwc->width < x || - y < bwc->y || bwc->y + bwc->height < y) - continue; - - /* Pass request into this child */ - return browser_window_scroll_at_point(bwc, - (x - bwc->x), (y - bwc->y), - scrx, scry); - } - } - - /* Try to scroll any current content */ - if (bw->current_content != NULL && content_scroll_at_point( - bw->current_content, x, y, scrx, scry) == true) - /* Scroll handled by current content */ - return true; - - /* Try to scroll this window, if scroll not already handled */ - if (handled_scroll == false) { - if (bw->scroll_y && scrollbar_scroll(bw->scroll_y, scry)) - handled_scroll = true; - - if (bw->scroll_x && scrollbar_scroll(bw->scroll_x, scrx)) - handled_scroll = true; - } - - return handled_scroll; -} - -/* exported interface, documented in browser.h */ -bool browser_window_drop_file_at_point(struct browser_window *bw, - int x, int y, char *file) -{ - assert(bw != NULL); - - /* Handle (i)frame scroll offset (core-managed browser windows only) */ - x += scrollbar_get_offset(bw->scroll_x); - y += scrollbar_get_offset(bw->scroll_y); - - if (bw->children) { - /* Browser window has children, so pass request on to - * appropriate child */ - struct browser_window *bwc; - int cur_child; - int children = bw->rows * bw->cols; - - /* Loop through all children of bw */ - for (cur_child = 0; cur_child < children; cur_child++) { - /* Set current child */ - bwc = &bw->children[cur_child]; - - /* Skip this frame if (x, y) coord lies outside */ - if (x < bwc->x || bwc->x + bwc->width < x || - y < bwc->y || bwc->y + bwc->height < y) - continue; - - /* Pass request into this child */ - return browser_window_drop_file_at_point(bwc, - (x - bwc->x), (y - bwc->y), - file); - } - } - - /* Pass file drop on to any content */ - if (bw->current_content != NULL) - return content_drop_file_at_point(bw->current_content, - x, y, file); - - return false; -} - -/* exported interface, documented in netsurf/browser_window.h */ -void browser_window_set_gadget_filename(struct browser_window *bw, - struct form_control *gadget, const char *fn) -{ - html_set_file_gadget_filename(bw->current_content, gadget, fn); -} - -/* exported interface, documented in browser.h */ -nserror browser_window_debug_dump(struct browser_window *bw, - FILE *f, enum content_debug op) -{ - if (bw->current_content != NULL) { - return content_debug_dump(bw->current_content, f, op); - } - return NSERROR_OK; -} - -/* exported interface, documented in browser.h */ -nserror browser_window_debug(struct browser_window *bw, enum content_debug op) -{ - if (bw->current_content != NULL) { - return content_debug(bw->current_content, op); - } - return NSERROR_OK; -} - -/** slow script handler -*/ -static bool slow_script(void *ctx) -{ - static int count = 0; - NSLOG(netsurf, INFO, "Continuing execution %d", count); - count++; - if (count > 1) { - count = 0; - return false; - } - return true; -} - -/* exported interface, documented in netsurf/browser_window.h */ -nserror browser_window_create(enum browser_window_create_flags flags, - nsurl *url, nsurl *referrer, - struct browser_window *existing, - struct browser_window **bw) -{ - gui_window_create_flags gw_flags = GW_CREATE_NONE; - struct browser_window *ret; - nserror err; - - /* Check parameters */ - if (flags & BW_CREATE_CLONE) { - if (existing == NULL) { - assert(0 && "Failed: No existing window provided."); - return NSERROR_BAD_PARAMETER; - } - } - if (!(flags & BW_CREATE_HISTORY)) { - if (!(flags & BW_CREATE_CLONE) || existing == NULL) { - assert(0 && "Failed: Must have existing for history."); - return NSERROR_BAD_PARAMETER; - } - } - - - if ((ret = calloc(1, sizeof(struct browser_window))) == NULL) { - return NSERROR_NOMEM; - } - - /* Initialise common parts */ - err = browser_window_initialise_common(flags, ret, existing); - if (err != NSERROR_OK) { - browser_window_destroy(ret); - return err; - } - - /* window characteristics */ - ret->browser_window_type = BROWSER_WINDOW_NORMAL; - ret->scrolling = BW_SCROLLING_YES; - ret->border = true; - ret->no_resize = true; - ret->focus = ret; - - /* initialise last action with creation time */ - nsu_getmonotonic_ms(&ret->last_action); - - /* The existing gui_window is on the top-level existing - * browser_window. */ - existing = browser_window_get_root(existing); - - /* Set up gui_window creation flags */ - if (flags & BW_CREATE_TAB) - gw_flags |= GW_CREATE_TAB; - if (flags & BW_CREATE_CLONE) - gw_flags |= GW_CREATE_CLONE; - - ret->window = guit->window->create(ret, - (existing != NULL) ? existing->window : NULL, - gw_flags); - - if (ret->window == NULL) { - browser_window_destroy(ret); - return NSERROR_BAD_PARAMETER; - } - - if (url != NULL) { - enum browser_window_nav_flags nav_flags = BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE; - if (flags & BW_CREATE_UNVERIFIABLE) - nav_flags |= BW_NAVIGATE_UNVERIFIABLE; - if (flags & BW_CREATE_HISTORY) - nav_flags |= BW_NAVIGATE_HISTORY; - browser_window_navigate(ret, url, referrer, nav_flags, NULL, - NULL, NULL); - } - - if (bw != NULL) { - *bw = ret; - } - - return NSERROR_OK; -} - - -/* exported internal interface, documented in desktop/browser_private.h */ -nserror browser_window_initialise_common(enum browser_window_create_flags flags, - struct browser_window *bw, struct browser_window *existing) -{ - nserror err; - assert(bw); - - /* new javascript context for each window/(i)frame */ - err = js_newcontext(nsoption_int(script_timeout), - slow_script, NULL, &bw->jsctx); - if (err != NSERROR_OK) - return err; - - if (flags & BW_CREATE_CLONE) { - assert(existing != NULL); - - /* clone history */ - err = browser_window_history_clone(existing, bw); - - /* copy the scale */ - bw->scale = existing->scale; - } else { - /* create history */ - err = browser_window_history_create(bw); - - /* default scale */ - bw->scale = (float) nsoption_int(scale) / 100.0; - } - - if (err != NSERROR_OK) - return err; - - /* window characteristics */ - bw->refresh_interval = -1; - - bw->drag.type = DRAGGING_NONE; - - bw->scroll_x = NULL; - bw->scroll_y = NULL; - - bw->focus = NULL; - - /* initialise status text cache */ - bw->status.text = NULL; - bw->status.text_len = 0; - bw->status.match = 0; - bw->status.miss = 0; - - return NSERROR_OK; -} - -/** - * implements the download operation of a window navigate - */ -static nserror -browser_window_download(struct browser_window *bw, - nsurl *url, - nsurl *nsref, - uint32_t fetch_flags, - bool fetch_is_post, - llcache_post_data *post) -{ - llcache_handle *l; - struct browser_window *root; - nserror error; - - root = browser_window_get_root(bw); - assert(root != NULL); - - fetch_flags |= LLCACHE_RETRIEVE_FORCE_FETCH; - fetch_flags |= LLCACHE_RETRIEVE_STREAM_DATA; - - error = llcache_handle_retrieve(url, fetch_flags, nsref, - fetch_is_post ? post : NULL, - NULL, NULL, &l); - if (error == NSERROR_NO_FETCH_HANDLER) { - /* no internal handler for this type, call out to frontend */ - error = guit->misc->launch_url(url); - } else if (error != NSERROR_OK) { - NSLOG(netsurf, INFO, "Failed to fetch download: %d", error); - } else { - error = download_context_create(l, root->window); - if (error != NSERROR_OK) { - NSLOG(netsurf, INFO, - "Failed creating download context: %d", error); - llcache_handle_abort(l); - llcache_handle_release(l); - } - } - - return error; -} - - -static bool browser_window_check_throbber(struct browser_window *bw) -{ - int children, index; - - if (bw->throbbing) - return true; - - if (bw->children) { - children = bw->rows * bw->cols; - for (index = 0; index < children; index++) { - if (browser_window_check_throbber(&bw->children[index])) - return true; - } - } - if (bw->iframes) { - for (index = 0; index < bw->iframe_count; index++) { - if (browser_window_check_throbber(&bw->iframes[index])) - return true; - } - } - return false; -} - - -/** - * Start the busy indicator. - * - * \param bw browser window - */ - -static void browser_window_start_throbber(struct browser_window *bw) -{ - bw->throbbing = true; - - while (bw->parent) - bw = bw->parent; - - guit->window->start_throbber(bw->window); -} - - -/** - * Stop the busy indicator. - * - * \param bw browser window - */ -static void browser_window_stop_throbber(struct browser_window *bw) -{ - bw->throbbing = false; - - while (bw->parent) - bw = bw->parent; - - if (!browser_window_check_throbber(bw)) { - guit->window->stop_throbber(bw->window); - } -} - - - -/** - * Callback for fetchcache() for browser window favicon fetches. - * - * \param c content handle of favicon - * \param event The event to process - * \param pw a context containing the browser window - * \return NSERROR_OK on success else appropriate error code. - */ -static nserror -browser_window_favicon_callback(hlcache_handle *c, - const hlcache_event *event, - void *pw) -{ - struct browser_window *bw = pw; - - switch (event->type) { - case CONTENT_MSG_DONE: - if (bw->favicon.current != NULL) { - content_status status = - content_get_status(bw->favicon.current); - - if ((status == CONTENT_STATUS_READY) || - (status == CONTENT_STATUS_DONE)) - content_close(bw->favicon.current); - - hlcache_handle_release(bw->favicon.current); - } - - bw->favicon.current = c; - bw->favicon.loading = NULL; - - /* content_get_bitmap on the hlcache_handle should give - * the favicon bitmap at this point - */ - guit->window->set_icon(bw->window, c); - break; - - case CONTENT_MSG_ERROR: - case CONTENT_MSG_ERRORCODE: - - /* clean up after ourselves */ - if (c == bw->favicon.loading) { - bw->favicon.loading = NULL; - } else if (c == bw->favicon.current) { - bw->favicon.current = NULL; - } - - hlcache_handle_release(c); - - if (bw->favicon.failed == false) { - nsurl *nsref = NULL; - nsurl *nsurl; - nserror error; - - bw->favicon.failed = true; - - error = nsurl_create("resource:favicon.ico", &nsurl); - if (error != NSERROR_OK) { - NSLOG(netsurf, INFO, - "Unable to create default location url"); - } else { - hlcache_handle_retrieve(nsurl, - HLCACHE_RETRIEVE_SNIFF_TYPE, - nsref, NULL, - browser_window_favicon_callback, - bw, NULL, CONTENT_IMAGE, - &bw->favicon.loading); - - nsurl_unref(nsurl); - } - - } - break; - - default: - break; - } - return NSERROR_OK; -} - - -/** - * update the favicon associated with the browser window - * - * \param c the page content handle. - * \param bw A top level browser window. - * \param link A link context or NULL to attempt fallback scanning. - */ -static void -browser_window_update_favicon(hlcache_handle *c, - struct browser_window *bw, - struct content_rfc5988_link *link) -{ - nsurl *nsref = NULL; - nsurl *nsurl; - nserror error; - - assert(c != NULL); - assert(bw !=NULL); - - if (bw->window == NULL) - /* Not top-level browser window; not interested */ - return; - - /* already fetching the favicon - use that */ - if (bw->favicon.loading != NULL) - return; - - bw->favicon.failed = false; - - if (link == NULL) { - /* Look for "icon" */ - link = content_find_rfc5988_link(c, corestring_lwc_icon); - } - - if (link == NULL) { - /* Look for "shortcut icon" */ - link = content_find_rfc5988_link(c, - corestring_lwc_shortcut_icon); - } - - if (link == NULL) { - lwc_string *scheme; - bool speculative_default = false; - bool match; - - nsurl = hlcache_handle_get_url(c); - - scheme = nsurl_get_component(nsurl, NSURL_SCHEME); - - /* If the document was fetched over http(s), then speculate - * that there's a favicon living at /favicon.ico */ - if ((lwc_string_caseless_isequal(scheme, corestring_lwc_http, - &match) == lwc_error_ok && match) || - (lwc_string_caseless_isequal(scheme, corestring_lwc_https, - &match) == lwc_error_ok && match)) { - speculative_default = true; - } - - lwc_string_unref(scheme); - - if (speculative_default) { - /* no favicon via link, try for the default location */ - error = nsurl_join(nsurl, "/favicon.ico", &nsurl); - } else { - bw->favicon.failed = true; - error = nsurl_create("resource:favicon.ico", &nsurl); - } - if (error != NSERROR_OK) { - NSLOG(netsurf, INFO, - "Unable to create default location url"); - return; - } - } else { - nsurl = nsurl_ref(link->href); - } - - if (link == NULL) { - NSLOG(netsurf, INFO, "fetching general favicon from '%s'", - nsurl_access(nsurl)); - } else { - NSLOG(netsurf, INFO, "fetching favicon rel:%s '%s'", - lwc_string_data(link->rel), nsurl_access(nsurl)); - } - - hlcache_handle_retrieve(nsurl, HLCACHE_RETRIEVE_SNIFF_TYPE, - nsref, NULL, browser_window_favicon_callback, - bw, NULL, CONTENT_IMAGE, &bw->favicon.loading); - - nsurl_unref(nsurl); -} - -/** - * window callback errorcode handling. - */ -static void -browser_window_callback_errorcode(hlcache_handle *c, - struct browser_window *bw, - nserror code) -{ - const char* message; - - message = messages_get_errorcode(code); - - browser_window_set_status(bw, message); - - /* Only warn the user about errors in top-level windows */ - if (bw->browser_window_type == BROWSER_WINDOW_NORMAL) { - guit->misc->warning(message, NULL); - } - - if (c == bw->loading_content) { - bw->loading_content = NULL; - } else if (c == bw->current_content) { - bw->current_content = NULL; - browser_window_remove_caret(bw, false); - } - - hlcache_handle_release(c); - - browser_window_stop_throbber(bw); -} - - -/** - * Handle meta http-equiv refresh time elapsing by loading a new page. - * - * \param p browser window to refresh with new page - */ -static void browser_window_refresh(void *p) -{ - struct browser_window *bw = p; - nsurl *url; - nsurl *refresh; - hlcache_handle *parent = NULL; - enum browser_window_nav_flags flags = BW_NAVIGATE_UNVERIFIABLE; - - assert(bw->current_content != NULL && - (content_get_status(bw->current_content) == - CONTENT_STATUS_READY || - content_get_status(bw->current_content) == - CONTENT_STATUS_DONE)); - - /* Ignore if the refresh URL has gone - * (may happen if a fetch error occurred) */ - refresh = content_get_refresh_url(bw->current_content); - if (refresh == NULL) - return; - - /* mark this content as invalid so it gets flushed from the cache */ - content_invalidate_reuse_data(bw->current_content); - - url = hlcache_handle_get_url(bw->current_content); - if ((url == NULL) || (nsurl_compare(url, refresh, NSURL_COMPLETE))) { - flags |= BW_NAVIGATE_HISTORY; - } - - /* Treat an (almost) immediate refresh in a top-level browser window as - * if it were an HTTP redirect, and thus make the resulting fetch - * verifiable. - * - * See fetchcache.c for why redirected fetches should be verifiable at - * all. - */ - if (bw->refresh_interval <= 100 && bw->parent == NULL) { - flags &= ~BW_NAVIGATE_UNVERIFIABLE; - } else { - parent = bw->current_content; - } - - browser_window_navigate(bw, - refresh, - url, - flags, - NULL, - NULL, - parent); - -} - - -/** - * Transfer the loading_content to a new download window. - */ - -static void browser_window_convert_to_download(struct browser_window *bw, - llcache_handle *stream) -{ - struct browser_window *root = browser_window_get_root(bw); - nserror error; - - assert(root != NULL); - - error = download_context_create(stream, root->window); - if (error != NSERROR_OK) { - llcache_handle_abort(stream); - llcache_handle_release(stream); - } - - /* remove content from browser window */ - hlcache_handle_release(bw->loading_content); - bw->loading_content = NULL; - - browser_window_stop_throbber(bw); -} - - -/** - * Browser window content event callback handler. - */ -static nserror -browser_window_callback(hlcache_handle *c, - const hlcache_event *event, - void *pw) -{ - struct browser_window *bw = pw; - nserror res = NSERROR_OK; - float sx, sy; - - switch (event->type) { - case CONTENT_MSG_DOWNLOAD: - assert(bw->loading_content == c); - - browser_window_convert_to_download(bw, event->data.download); - - if (bw->current_content != NULL) { - browser_window_refresh_url_bar(bw); - } - break; - - case CONTENT_MSG_LOADING: - assert(bw->loading_content == c); - -#ifdef WITH_THEME_INSTALL - if (content_get_type(c) == CONTENT_THEME) { - theme_install_start(c); - bw->loading_content = NULL; - browser_window_stop_throbber(bw); - } else -#endif - { - bw->refresh_interval = -1; - browser_window_set_status(bw, - content_get_status_message(c)); - } - break; - - case CONTENT_MSG_READY: - { - int width, height; - - assert(bw->loading_content == c); - - if (bw->current_content != NULL) { - content_status status = - content_get_status(bw->current_content); - - if (status == CONTENT_STATUS_READY || - status == CONTENT_STATUS_DONE) - content_close(bw->current_content); - - hlcache_handle_release(bw->current_content); - } - - bw->current_content = c; - bw->loading_content = NULL; - - /* history */ - if (bw->history_add && bw->history) { - nsurl *url = hlcache_handle_get_url(c); - - if (urldb_add_url(url)) { - urldb_set_url_title(url, content_get_title(c)); - urldb_update_url_visit_data(url); - urldb_set_url_content_type(url, - content_get_type(c)); - - /* This is safe as we've just added the URL */ - global_history_add(urldb_get_url(url)); - } - /** \todo Urldb / Thumbnails / Local history brokenness - * - * We add to local history after calling urldb_add_url - * rather than in the block above. If urldb_add_url - * fails (as it will for urls like "about:about", - * "about:config" etc), there would be no local history - * node, and later calls to history_update will either - * explode or overwrite the node for the previous URL. - * - * We call it after, rather than before urldb_add_url - * because history_add calls bitmap render, which - * tries to register the thumbnail with urldb. That - * thumbnail registration fails if the url doesn't - * exist in urldb already, and only urldb-registered - * thumbnails get freed. So if we called history_add - * before urldb_add_url we would leak thumbnails for - * all newly visited URLs. With the history_add call - * after, we only leak the thumbnails when urldb does - * not add the URL. - */ - browser_window_history_add(bw, c, bw->frag_id); - } - - /* Format the new content to the correct dimensions */ - browser_window_get_dimensions(bw, &width, &height, true); - content_reformat(c, false, width, height); - - browser_window_remove_caret(bw, false); - - if (bw->window != NULL) { - guit->window->new_content(bw->window); - - browser_window_refresh_url_bar(bw); - } - - /* new content; set scroll_to_top */ - browser_window_update(bw, true); - content_open(c, bw, 0, 0); - browser_window_set_status(bw, content_get_status_message(c)); - - /* frames */ - if ((content_get_type(c) == CONTENT_HTML) && - (html_get_frameset(c) != NULL)) { - res = browser_window_create_frameset(bw, - html_get_frameset(c)); - } - if (content_get_type(c) == CONTENT_HTML && - html_get_iframe(c) != NULL) - browser_window_create_iframes(bw, html_get_iframe(c)); - } - break; - - case CONTENT_MSG_DONE: - assert(bw->current_content == c); - - if (bw->window == NULL) { - /* Updated browser window's scrollbars. - * TODO: do this before CONTENT_MSG_DONE */ - browser_window_reformat(bw, true, - bw->width, bw->height); - browser_window_handle_scrollbars(bw); - } - - browser_window_update(bw, false); - browser_window_set_status(bw, content_get_status_message(c)); - browser_window_stop_throbber(bw); - browser_window_update_favicon(c, bw, NULL); - - if (browser_window_history_get_scroll(bw, &sx, &sy) == NSERROR_OK) { - int scrollx = (int)((float)content_get_width(bw->current_content) * sx); - int scrolly = (int)((float)content_get_height(bw->current_content) * sy); - struct rect rect; - rect.x0 = rect.x1 = scrollx; - rect.y0 = rect.y1 = scrolly; - if (browser_window_set_scroll(bw, &rect) != NSERROR_OK) { - NSLOG(netsurf, WARNING, - "Unable to set browser scroll offsets to %d by %d", - scrollx, scrolly); - } - } - - browser_window_history_update(bw, c); - hotlist_update_url(hlcache_handle_get_url(c)); - - if (bw->refresh_interval != -1) { - guit->misc->schedule(bw->refresh_interval * 10, - browser_window_refresh, bw); - } - break; - - case CONTENT_MSG_ERRORCODE: - browser_window_callback_errorcode(c, bw, event->data.errorcode); - break; - - case CONTENT_MSG_ERROR: - browser_window_set_status(bw, event->data.error); - - /* Only warn the user about errors in top-level windows */ - if (bw->browser_window_type == BROWSER_WINDOW_NORMAL) { - guit->misc->warning(event->data.error, NULL); - } - - if (c == bw->loading_content) - bw->loading_content = NULL; - else if (c == bw->current_content) { - bw->current_content = NULL; - browser_window_remove_caret(bw, false); - } - - hlcache_handle_release(c); - - browser_window_stop_throbber(bw); - break; - - case CONTENT_MSG_REDIRECT: - if (urldb_add_url(event->data.redirect.from)) - urldb_update_url_visit_data(event->data.redirect.from); - break; - - case CONTENT_MSG_STATUS: - if (event->data.explicit_status_text == NULL) { - /* Object content's status text updated */ - const char *status = NULL; - if (bw->loading_content != NULL) - /* Give preference to any loading content */ - status = content_get_status_message( - bw->loading_content); - - if (status == NULL) - status = content_get_status_message(c); - - if (status != NULL) - browser_window_set_status(bw, status); - } else { - /* Object content wants to set explicit message */ - browser_window_set_status(bw, - event->data.explicit_status_text); - } - break; - - case CONTENT_MSG_REFORMAT: - if (c == bw->current_content && - content_get_type(c) == CONTENT_HTML) { - /* reposition frames */ - if (html_get_frameset(c) != NULL) - browser_window_recalculate_frameset(bw); - /* reflow iframe positions */ - if (html_get_iframe(c) != NULL) - browser_window_recalculate_iframes(bw); - } - - /* Hide any caret, but don't remove it */ - browser_window_remove_caret(bw, true); - - if (!(event->data.background)) { - /* Reformatted content should be redrawn */ - browser_window_update(bw, false); - } - break; - - case CONTENT_MSG_REDRAW: - { - struct rect rect = { - .x0 = event->data.redraw.x, - .y0 = event->data.redraw.y, - .x1 = event->data.redraw.x + event->data.redraw.width, - .y1 = event->data.redraw.y + event->data.redraw.height - }; - - browser_window_update_box(bw, &rect); - } - break; - - case CONTENT_MSG_REFRESH: - bw->refresh_interval = event->data.delay * 100; - break; - - case CONTENT_MSG_LINK: /* content has an rfc5988 link element */ - { - bool match; - - /* Handle "icon" and "shortcut icon" */ - if ((lwc_string_caseless_isequal( - event->data.rfc5988_link->rel, - corestring_lwc_icon, - &match) == lwc_error_ok && match) || - (lwc_string_caseless_isequal( - event->data.rfc5988_link->rel, - corestring_lwc_shortcut_icon, - &match) == lwc_error_ok && match)) { - /* it's a favicon perhaps start a fetch for it */ - browser_window_update_favicon(c, bw, - event->data.rfc5988_link); - } - } - break; - - case CONTENT_MSG_GETCTX: - /* only the content object created by the browser - * window requires a new global compartment object - */ - assert(bw->loading_content == c); - if (js_newcompartment(bw->jsctx, - bw, - hlcache_handle_get_content(c)) != NULL) { - *(event->data.jscontext) = bw->jsctx; - } - break; - - case CONTENT_MSG_SCROLL: - { - struct rect rect = { - .x0 = event->data.scroll.x0, - .y0 = event->data.scroll.y0, - }; - - /* Content wants to be scrolled */ - if (bw->current_content != c) { - break; - } - - if (event->data.scroll.area) { - rect.x1 = event->data.scroll.x1; - rect.y1 = event->data.scroll.y1; - } else { - rect.x1 = event->data.scroll.x0; - rect.y1 = event->data.scroll.y0; - } - browser_window_set_scroll(bw, &rect); - - break; - } - - case CONTENT_MSG_DRAGSAVE: - { - /* Content wants drag save of a content */ - struct browser_window *root = browser_window_get_root(bw); - hlcache_handle *save = event->data.dragsave.content; - - if (save == NULL) { - save = c; - } - - switch(event->data.dragsave.type) { - case CONTENT_SAVE_ORIG: - guit->window->drag_save_object(root->window, save, - GUI_SAVE_OBJECT_ORIG); - break; - - case CONTENT_SAVE_NATIVE: - guit->window->drag_save_object(root->window, save, - GUI_SAVE_OBJECT_NATIVE); - break; - - case CONTENT_SAVE_COMPLETE: - guit->window->drag_save_object(root->window, save, - GUI_SAVE_COMPLETE); - break; - - case CONTENT_SAVE_SOURCE: - guit->window->drag_save_object(root->window, save, - GUI_SAVE_SOURCE); - break; - } - } - break; - - case CONTENT_MSG_SAVELINK: - { - /* Content wants a link to be saved */ - struct browser_window *root = browser_window_get_root(bw); - guit->window->save_link(root->window, - event->data.savelink.url, - event->data.savelink.title); - } - break; - - case CONTENT_MSG_POINTER: - /* Content wants to have specific mouse pointer */ - browser_window_set_pointer(bw, event->data.pointer); - break; - - case CONTENT_MSG_DRAG: - { - browser_drag_type bdt = DRAGGING_NONE; - - switch (event->data.drag.type) { - case CONTENT_DRAG_NONE: - bdt = DRAGGING_NONE; - break; - case CONTENT_DRAG_SCROLL: - bdt = DRAGGING_CONTENT_SCROLLBAR; - break; - case CONTENT_DRAG_SELECTION: - bdt = DRAGGING_SELECTION; - break; - } - browser_window_set_drag_type(bw, bdt, event->data.drag.rect); - } - break; - - case CONTENT_MSG_CARET: - switch (event->data.caret.type) { - case CONTENT_CARET_REMOVE: - browser_window_remove_caret(bw, false); - break; - case CONTENT_CARET_HIDE: - browser_window_remove_caret(bw, true); - break; - case CONTENT_CARET_SET_POS: - browser_window_place_caret(bw, - event->data.caret.pos.x, - event->data.caret.pos.y, - event->data.caret.pos.height, - event->data.caret.pos.clip); - break; - } - break; - - case CONTENT_MSG_SELECTION: - browser_window_set_selection(bw, - event->data.selection.selection, - event->data.selection.read_only); - break; - - case CONTENT_MSG_SELECTMENU: - if (event->data.select_menu.gadget->type == GADGET_SELECT) { - struct browser_window *root = - browser_window_get_root(bw); - guit->window->create_form_select_menu(root->window, - event->data.select_menu.gadget); - } - - break; - - case CONTENT_MSG_GADGETCLICK: - if (event->data.gadget_click.gadget->type == GADGET_FILE) { - struct browser_window *root = - browser_window_get_root(bw); - guit->window->file_gadget_open(root->window, c, - event->data.gadget_click.gadget); - } - - break; - - default: - break; - } - return res; -} - - -/* Have to forward declare browser_window_destroy_internal */ -static void browser_window_destroy_internal(struct browser_window *bw); - -/** - * Close and destroy all child browser window. - * - * \param bw browser window - */ -static void browser_window_destroy_children(struct browser_window *bw) -{ - int i; - - if (bw->children) { - for (i = 0; i < (bw->rows * bw->cols); i++) - browser_window_destroy_internal(&bw->children[i]); - free(bw->children); - bw->children = NULL; - bw->rows = 0; - bw->cols = 0; - } - if (bw->iframes) { - for (i = 0; i < bw->iframe_count; i++) - browser_window_destroy_internal(&bw->iframes[i]); - free(bw->iframes); - bw->iframes = NULL; - bw->iframe_count = 0; - } -} - - -/** - * internal scheduled reformat callback. - * - * scheduled reformat callback to allow reformats from unthreaded context. - * - * \param vbw The browser window to be reformatted - */ -static void scheduled_reformat(void *vbw) -{ - struct browser_window *bw = vbw; - int width; - int height; - nserror res; - - res = guit->window->get_dimensions(bw->window, &width, &height, false); - if (res == NSERROR_OK) { - browser_window_reformat(bw, false, width, height); - } -} - - -/** - * Release all memory associated with a browser window. - * - * \param bw browser window - */ -static void browser_window_destroy_internal(struct browser_window *bw) -{ - assert(bw); - - NSLOG(netsurf, INFO, "Destroying window"); - - if (bw->children != NULL || bw->iframes != NULL) { - browser_window_destroy_children(bw); - } - - /* Destroy scrollbars */ - if (bw->scroll_x != NULL) { - scrollbar_destroy(bw->scroll_x); - } - - if (bw->scroll_y != NULL) { - scrollbar_destroy(bw->scroll_y); - } - - /* clear any pending callbacks */ - guit->misc->schedule(-1, browser_window_refresh, bw); - /* The ugly cast here is so the reformat function can be - * passed a gui window pointer in its API rather than void* - */ - NSLOG(netsurf, INFO, - "Clearing reformat schedule for browser window %p", bw); - guit->misc->schedule(-1, scheduled_reformat, bw); - - /* If this brower window is not the root window, and has focus, unset - * the root browser window's focus pointer. */ - if (!bw->window) { - struct browser_window *top = browser_window_get_root(bw); - - if (top->focus == bw) - top->focus = top; - - if (top->selection.bw == bw) { - browser_window_set_selection(top, false, false); - } - } - - /* Destruction order is important: we must ensure that the frontend - * destroys any window(s) associated with this browser window before - * we attempt any destructive cleanup. - */ - - if (bw->window) { - /* Only the root window has a GUI window */ - guit->window->destroy(bw->window); - } - - if (bw->loading_content != NULL) { - hlcache_handle_abort(bw->loading_content); - hlcache_handle_release(bw->loading_content); - bw->loading_content = NULL; - } - - if (bw->current_content != NULL) { - content_status status = content_get_status(bw->current_content); - if (status == CONTENT_STATUS_READY || - status == CONTENT_STATUS_DONE) - content_close(bw->current_content); - - hlcache_handle_release(bw->current_content); - bw->current_content = NULL; - } - - if (bw->favicon.loading != NULL) { - hlcache_handle_abort(bw->favicon.loading); - hlcache_handle_release(bw->favicon.loading); - bw->favicon.loading = NULL; - } - - if (bw->favicon.current != NULL) { - content_status status = content_get_status(bw->favicon.current); - - if (status == CONTENT_STATUS_READY || - status == CONTENT_STATUS_DONE) { - content_close(bw->favicon.current); - } - - hlcache_handle_release(bw->favicon.current); - bw->favicon.current = NULL; - } - - if (bw->box != NULL) { - bw->box->iframe = NULL; - bw->box = NULL; - } - - if (bw->jsctx != NULL) { - js_destroycontext(bw->jsctx); - } - - /* These simply free memory, so are safe here */ - - if (bw->frag_id != NULL) { - lwc_string_unref(bw->frag_id); - } - - browser_window_history_destroy(bw); - - free(bw->name); - free(bw->status.text); - bw->status.text = NULL; - NSLOG(netsurf, INFO, "Status text cache match:miss %d:%d", - bw->status.match, bw->status.miss); -} - -/** - * Update URL bar for a given browser window to given URL - * - * \param bw Browser window to update URL bar for. - * \param url URL for content displayed by bw including any fragment. - */ -static inline nserror -browser_window_refresh_url_bar_internal(struct browser_window *bw, nsurl *url) -{ - assert(bw); - assert(url); - - if ((bw->parent != NULL) || (bw->window == NULL)) { - /* Not root window or no gui window so do not set a URL */ - return NSERROR_OK; - } - - return guit->window->set_url(bw->window, url); -} - - -/* exported interface, documented in netsurf/browser_window.h */ -void browser_window_destroy(struct browser_window *bw) -{ - /* can't destoy child windows on their own */ - assert(!bw->parent); - - /* destroy */ - browser_window_destroy_internal(bw); - free(bw); -} - -/* exported interface, documented in netsurf/browser_window.h */ -nserror browser_window_refresh_url_bar(struct browser_window *bw) -{ - nserror ret; - nsurl *display_url; - - assert(bw); - - if (bw->parent != NULL) { - /* Not root window; don't set a URL in GUI URL bar */ - return NSERROR_OK; - } - - if (bw->current_content == NULL) { - /* no content so return about:blank */ - ret = browser_window_refresh_url_bar_internal(bw, - corestring_nsurl_about_blank); - } else if (bw->frag_id == NULL) { - ret = browser_window_refresh_url_bar_internal(bw, - hlcache_handle_get_url(bw->current_content)); - } else { - /* Combine URL and Fragment */ - ret = nsurl_refragment( - hlcache_handle_get_url(bw->current_content), - bw->frag_id, &display_url); - if (ret == NSERROR_OK) { - ret = browser_window_refresh_url_bar_internal(bw, - display_url); - nsurl_unref(display_url); - } - } - - return ret; -} - - -/* exported interface documented in netsurf/browser_window.h */ -nserror -browser_window_navigate(struct browser_window *bw, - nsurl *url, - nsurl *referrer, - enum browser_window_nav_flags flags, - char *post_urlenc, - struct fetch_multipart_data *post_multipart, - hlcache_handle *parent) -{ - hlcache_handle *c; - int depth = 0; - struct browser_window *cur; - uint32_t fetch_flags = 0; - bool fetch_is_post = (post_urlenc != NULL || post_multipart != NULL); - llcache_post_data post; - hlcache_child_context child; - nserror error; - - assert(bw); - assert(url); - - NSLOG(netsurf, INFO, "bw %p, url %s", bw, nsurl_access(url)); - - /* If we're navigating and we have a history entry and a content - * then update the history entry before we navigate to save our - * current state. However since history navigation pre-moves - * the history state, we ensure that we only do this if we've not - * been suppressed. In the suppressed case, the history code - * updates the history itself before navigating. - */ - if (bw->current_content != NULL && - bw->history != NULL && - bw->history->current != NULL && - !(flags & BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE)) { - browser_window_history_update(bw, bw->current_content); - } - - /* don't allow massively nested framesets */ - for (cur = bw; cur->parent; cur = cur->parent) { - depth++; - } - if (depth > FRAME_DEPTH) { - NSLOG(netsurf, INFO, "frame depth too high."); - return NSERROR_FRAME_DEPTH; - } - - /* Set up retrieval parameters */ - if (!(flags & BW_NAVIGATE_UNVERIFIABLE)) { - fetch_flags |= LLCACHE_RETRIEVE_VERIFIABLE; - } - - if (post_multipart != NULL) { - post.type = LLCACHE_POST_MULTIPART; - post.data.multipart = post_multipart; - } else if (post_urlenc != NULL) { - post.type = LLCACHE_POST_URL_ENCODED; - post.data.urlenc = post_urlenc; - } - - child.charset = content_get_encoding(parent, CONTENT_ENCODING_NORMAL); - if ((parent != NULL) && (content_get_type(parent) == CONTENT_HTML)) { - child.quirks = content_get_quirks(parent); - } - - url = nsurl_ref(url); - - if (referrer != NULL) { - referrer = nsurl_ref(referrer); - } - - /* Get download out of the way */ - if ((flags & BW_NAVIGATE_DOWNLOAD) != 0) { - error = browser_window_download(bw, - url, - referrer, - fetch_flags, - fetch_is_post, - &post); - nsurl_unref(url); - if (referrer != NULL) { - nsurl_unref(referrer); - } - return error; - } - - if (bw->frag_id != NULL) { - lwc_string_unref(bw->frag_id); - } - bw->frag_id = NULL; - - if (nsurl_has_component(url, NSURL_FRAGMENT)) { - bool same_url = false; - - bw->frag_id = nsurl_get_component(url, NSURL_FRAGMENT); - - /* Compare new URL with existing one (ignoring fragments) */ - if ((bw->current_content != NULL) && - (hlcache_handle_get_url(bw->current_content) != NULL)) { - same_url = nsurl_compare(url, - hlcache_handle_get_url( - bw->current_content), - NSURL_COMPLETE); - } - - /* if we're simply moving to another ID on the same page, - * don't bother to fetch, just update the window. - */ - if ((same_url) && - (fetch_is_post == false) && - (nsurl_has_component(url, NSURL_QUERY) == false)) { - nsurl_unref(url); - - if (referrer != NULL) { - nsurl_unref(referrer); - } - - if ((flags & BW_NAVIGATE_HISTORY) != 0) { - browser_window_history_add(bw, - bw->current_content, bw->frag_id); - } - - browser_window_update(bw, false); - - if (bw->current_content != NULL) { - browser_window_refresh_url_bar(bw); - } - return NSERROR_OK; - } - } - - browser_window_stop(bw); - browser_window_remove_caret(bw, false); - browser_window_destroy_children(bw); - - NSLOG(netsurf, INFO, "Loading '%s'", nsurl_access(url)); - - browser_window_set_status(bw, messages_get("Loading")); - bw->history_add = (flags & BW_NAVIGATE_HISTORY); - - /* Verifiable fetches may trigger a download */ - if (!(flags & BW_NAVIGATE_UNVERIFIABLE)) { - fetch_flags |= HLCACHE_RETRIEVE_MAY_DOWNLOAD; - } - - error = hlcache_handle_retrieve(url, - fetch_flags | HLCACHE_RETRIEVE_SNIFF_TYPE, - referrer, - fetch_is_post ? &post : NULL, - browser_window_callback, bw, - parent != NULL ? &child : NULL, - CONTENT_ANY, &c); - - switch (error) { - case NSERROR_OK: - bw->loading_content = c; - browser_window_start_throbber(bw); - error = browser_window_refresh_url_bar_internal(bw, url); - break; - - case NSERROR_NO_FETCH_HANDLER: /* no handler for this type */ - /** \todo does this always try and download even - * unverifiable content? - */ - error = guit->misc->launch_url(url); - break; - - default: /* report error to user */ - browser_window_set_status(bw, messages_get_errorcode(error)); - /** @todo should the caller report the error? */ - guit->misc->warning(messages_get_errorcode(error), NULL); - break; - - } - - nsurl_unref(url); - if (referrer != NULL) { - nsurl_unref(referrer); - } - - /* Record time */ - nsu_getmonotonic_ms(&bw->last_action); - - return error; -} - - -/* Exported interface, documented in browser.h */ -bool browser_window_up_available(struct browser_window *bw) -{ - bool result = false; - - if (bw != NULL && bw->current_content != NULL) { - nsurl *parent; - nserror err = nsurl_parent(hlcache_handle_get_url( - bw->current_content), &parent); - if (err == NSERROR_OK) { - result = nsurl_compare(hlcache_handle_get_url( - bw->current_content), parent, - NSURL_COMPLETE) == false; - nsurl_unref(parent); - } - } - - return result; -} - - -/* Exported interface, documented in browser.h */ -nserror browser_window_navigate_up(struct browser_window *bw, bool new_window) -{ - nsurl *current, *parent; - nserror err; - - if (bw == NULL) - return NSERROR_BAD_PARAMETER; - - current = browser_window_get_url(bw); - - err = nsurl_parent(current, &parent); - if (err != NSERROR_OK) { - return err; - } - - if (nsurl_compare(current, parent, NSURL_COMPLETE) == true) { - /* Can't go up to parent from here */ - nsurl_unref(parent); - return NSERROR_OK; - } - - if (new_window) { - err = browser_window_create(BW_CREATE_CLONE, - parent, NULL, bw, NULL); - } else { - err = browser_window_navigate(bw, parent, NULL, - BW_NAVIGATE_HISTORY, NULL, NULL, NULL); - } - - nsurl_unref(parent); - return err; -} - - -/* Exported interface, documented in browser.h */ -nsurl* browser_window_get_url(struct browser_window *bw) -{ - assert(bw != NULL); - - if (bw->current_content != NULL) { - return hlcache_handle_get_url(bw->current_content); - - } else if (bw->loading_content != NULL) { - /* TODO: should we return this? */ - return hlcache_handle_get_url(bw->loading_content); - } - - return corestring_nsurl_about_blank; -} - -/* Exported interface, documented in browser.h */ -const char* browser_window_get_title(struct browser_window *bw) -{ - assert(bw != NULL); - - if (bw->current_content != NULL) { - return content_get_title(bw->current_content); - } - - /* no content so return about:blank */ - return nsurl_access(corestring_nsurl_about_blank); -} - -/* Exported interface, documented in browser.h */ -struct history * browser_window_get_history(struct browser_window *bw) -{ - assert(bw != NULL); - - return bw->history; -} - - -/* Exported interface, documented in browser.h */ -bool browser_window_has_content(struct browser_window *bw) -{ - assert(bw != NULL); - - if (bw->current_content == NULL) { - return false; - } - - return true; -} - -/* Exported interface, documented in browser.h */ -struct hlcache_handle *browser_window_get_content(struct browser_window *bw) -{ - return bw->current_content; -} - -/* Exported interface, documented in browser.h */ -nserror browser_window_get_extents(struct browser_window *bw, bool scaled, - int *width, int *height) -{ - assert(bw != NULL); - - if (bw->current_content == NULL) { - *width = 0; - *height = 0; - return NSERROR_BAD_CONTENT; - } - - *width = content_get_width(bw->current_content); - *height = content_get_height(bw->current_content); - - if (scaled) { - *width *= bw->scale; - *height *= bw->scale; - } - - return NSERROR_OK; -} - - -/* exported internal interface, documented in desktop/browser_private.h */ -void browser_window_get_dimensions(struct browser_window *bw, - int *width, int *height, bool scaled) -{ - assert(bw); - - if (bw->window == NULL) { - /* Core managed browser window */ - *width = bw->width; - *height = bw->height; - } else { - /* Front end window */ - guit->window->get_dimensions(bw->window, width, height, scaled); - } -} - - -/* Exported interface, documented in browser.h */ -void browser_window_set_dimensions(struct browser_window *bw, - int width, int height) -{ - assert(bw); - - if (bw->window == NULL) { - /* Core managed browser window */ - bw->width = width; - bw->height = height; - } else { - NSLOG(netsurf, INFO, - "Asked to set dimensions of front end window."); - assert(0); - } -} - - -/** - * scroll to a fragment if present - * - * \param bw browser window - * \return true if the scroll was sucessful - */ -static bool frag_scroll(struct browser_window *bw) -{ - struct rect rect; - - if (bw->frag_id == NULL) { - return false; - } - - if (!html_get_id_offset(bw->current_content, - bw->frag_id, - &rect.x0, - &rect.y0)) { - return false; - } - - rect.x1 = rect.x0; - rect.y1 = rect.y0; - if (browser_window_set_scroll(bw, &rect) == NSERROR_OK) { - if (bw->current_content != NULL && - bw->history != NULL && - bw->history->current != NULL) { - browser_window_history_update(bw, bw->current_content); - } - return true; - } - return false; -} - -/* Exported interface, documented in browser.h */ -void browser_window_update(struct browser_window *bw, bool scroll_to_top) -{ - static const struct rect zrect = { - .x0 = 0, - .y0 = 0, - .x1 = 0, - .y1 = 0 - }; - - if (bw->current_content == NULL) { - return; - } - - switch (bw->browser_window_type) { - - case BROWSER_WINDOW_NORMAL: - /* Root browser window, constituting a front end window/tab */ - guit->window->set_title(bw->window, - content_get_title(bw->current_content)); - - browser_window_update_extent(bw); - - /* if frag_id exists, then try to scroll to it */ - /** @todo don't do this if the user has scrolled */ - if (!frag_scroll(bw)) { - if (scroll_to_top) { - browser_window_set_scroll(bw, &zrect); - } - } - - guit->window->invalidate(bw->window, NULL); - - break; - - case BROWSER_WINDOW_IFRAME: - /* Internal iframe browser window */ - assert(bw->parent != NULL); - assert(bw->parent->current_content != NULL); - - browser_window_update_extent(bw); - - if (scroll_to_top) { - browser_window_set_scroll(bw, &zrect); - } - - /* if frag_id exists, then try to scroll to it */ - /** @todo don't do this if the user has scrolled */ - frag_scroll(bw); - - html_redraw_a_box(bw->parent->current_content, bw->box); - break; - - case BROWSER_WINDOW_FRAME: - { - struct rect rect; - browser_window_update_extent(bw); - - if (scroll_to_top) { - browser_window_set_scroll(bw, &zrect); - } - - /* if frag_id exists, then try to scroll to it */ - /** @todo don't do this if the user has scrolled */ - frag_scroll(bw); - - rect.x0 = scrollbar_get_offset(bw->scroll_x); - rect.y0 = scrollbar_get_offset(bw->scroll_y); - rect.x1 = rect.x0 + bw->width; - rect.y1 = rect.y0 + bw->height; - - browser_window_update_box(bw, &rect); - } - break; - - default: - case BROWSER_WINDOW_FRAMESET: - /* Nothing to do */ - break; - } -} - -/* Exported interface, documented in netsurf/browser_window.h */ -void browser_window_update_box(struct browser_window *bw, struct rect *rect) -{ - int pos_x; - int pos_y; - struct browser_window *top; - - assert(bw); - - if (bw->window != NULL) { - /* Front end window */ - guit->window->invalidate(bw->window, rect); - } else { - /* Core managed browser window */ - browser_window_get_position(bw, true, &pos_x, &pos_y); - - top = browser_window_get_root(bw); - - rect->x0 += pos_x / bw->scale; - rect->y0 += pos_y / bw->scale; - rect->x1 += pos_x / bw->scale; - rect->y1 += pos_y / bw->scale; - - guit->window->invalidate(top->window, rect); - } -} - -/* Exported interface, documented in netsurf/browser_window.h */ -void browser_window_stop(struct browser_window *bw) -{ - int children, index; - - if (bw->loading_content != NULL) { - hlcache_handle_abort(bw->loading_content); - hlcache_handle_release(bw->loading_content); - bw->loading_content = NULL; - } - - if (bw->current_content != NULL && content_get_status( - bw->current_content) != CONTENT_STATUS_DONE) { - nserror error; - assert(content_get_status(bw->current_content) == - CONTENT_STATUS_READY); - error = hlcache_handle_abort(bw->current_content); - assert(error == NSERROR_OK); - } - - guit->misc->schedule(-1, browser_window_refresh, bw); - - if (bw->children) { - children = bw->rows * bw->cols; - for (index = 0; index < children; index++) - browser_window_stop(&bw->children[index]); - } - if (bw->iframes) { - children = bw->iframe_count; - for (index = 0; index < children; index++) - browser_window_stop(&bw->iframes[index]); - } - - if (bw->current_content != NULL) { - browser_window_refresh_url_bar(bw); - } - - browser_window_stop_throbber(bw); -} - - -/* Exported interface, documented in netsurf/browser_window.h */ -void browser_window_reload(struct browser_window *bw, bool all) -{ - hlcache_handle *c; - unsigned int i; - - if (bw->current_content == NULL || bw->loading_content != NULL) - return; - - if (all && content_get_type(bw->current_content) == CONTENT_HTML) { - struct html_stylesheet *sheets; - struct content_html_object *object; - unsigned int count; - - c = bw->current_content; - - /* invalidate objects */ - object = html_get_objects(c, &count); - - for (; object != NULL; object = object->next) { - if (object->content != NULL) - content_invalidate_reuse_data(object->content); - } - - /* invalidate stylesheets */ - sheets = html_get_stylesheets(c, &count); - - for (i = STYLESHEET_START; i != count; i++) { - if (sheets[i].sheet != NULL) { - content_invalidate_reuse_data(sheets[i].sheet); - } - } - } - - content_invalidate_reuse_data(bw->current_content); - - browser_window_navigate(bw, - hlcache_handle_get_url(bw->current_content), - NULL, - BW_NAVIGATE_NONE, - NULL, - NULL, - NULL); -} - - -/* Exported interface, documented in netsurf/browser_window.h */ -void browser_window_set_status(struct browser_window *bw, const char *text) -{ - int text_len; - /* find topmost window */ - while (bw->parent) - bw = bw->parent; - - if ((bw->status.text != NULL) && - (strcmp(text, bw->status.text) == 0)) { - /* status text is unchanged */ - bw->status.match++; - return; - } - - /* status text is changed */ - - text_len = strlen(text); - - if ((bw->status.text == NULL) || (bw->status.text_len < text_len)) { - /* no current string allocation or it is not long enough */ - free(bw->status.text); - bw->status.text = strdup(text); - bw->status.text_len = text_len; - } else { - /* current allocation has enough space */ - memcpy(bw->status.text, text, text_len + 1); - } - - bw->status.miss++; - guit->window->set_status(bw->window, bw->status.text); -} - - -/* Exported interface, documented in netsurf/browser_window.h */ -void browser_window_set_pointer(struct browser_window *bw, - browser_pointer_shape shape) -{ - struct browser_window *root = browser_window_get_root(bw); - gui_pointer_shape gui_shape; - bool loading; - uint64_t ms_now; - - assert(root); - assert(root->window); - - loading = ((bw->loading_content != NULL) || - ((bw->current_content != NULL) && - (content_get_status(bw->current_content) == CONTENT_STATUS_READY))); - - nsu_getmonotonic_ms(&ms_now); - - if (loading && ((ms_now - bw->last_action) < 1000)) { - /* If loading and less than 1 second since last link followed, - * force progress indicator pointer */ - gui_shape = GUI_POINTER_PROGRESS; - - } else if (shape == BROWSER_POINTER_AUTO) { - /* Up to browser window to decide */ - if (loading) { - gui_shape = GUI_POINTER_PROGRESS; - } else { - gui_shape = GUI_POINTER_DEFAULT; - } - - } else { - /* Use what we were told */ - gui_shape = (gui_pointer_shape)shape; - } - - guit->window->set_pointer(root->window, gui_shape); -} - - -/* exported function documented in netsurf/browser_window.h */ -nserror browser_window_schedule_reformat(struct browser_window *bw) -{ - if (bw->window == NULL) { - return NSERROR_BAD_PARAMETER; - } - - guit->misc->schedule(0, scheduled_reformat, bw); - - return NSERROR_OK; -} - - -/* exported function documented in netsurf/browser_window.h */ -void browser_window_reformat(struct browser_window *bw, bool background, - int width, int height) -{ - hlcache_handle *c = bw->current_content; - - if (c == NULL) - return; - - if (bw->browser_window_type != BROWSER_WINDOW_IFRAME) { - /* Iframe dimensions are already scaled in parent's layout */ - width /= bw->scale; - height /= bw->scale; - } - - if (bw->window == NULL) { - /* Core managed browser window; subtract scrollbar width */ - width -= bw->scroll_y ? SCROLLBAR_WIDTH : 0; - height -= bw->scroll_x ? SCROLLBAR_WIDTH : 0; - - width = width > 0 ? width : 0; - height = height > 0 ? height : 0; - } - - content_reformat(c, background, width, height); -} - -/** - * Set browser window scale. - * - * \param bw Browser window. - * \param scale value. - */ -static void browser_window_set_scale_internal(struct browser_window *bw, - float scale) -{ - int i; - hlcache_handle *c; - - if (fabs(bw->scale-scale) < 0.0001) - return; - - bw->scale = scale; - c = bw->current_content; - - if (c != NULL) { - if (content_can_reformat(c) == false) { - browser_window_update(bw, false); - } else { - browser_window_schedule_reformat(bw); - } - } - - for (i = 0; i < (bw->cols * bw->rows); i++) - browser_window_set_scale_internal(&bw->children[i], scale); - for (i = 0; i < bw->iframe_count; i++) - browser_window_set_scale_internal(&bw->iframes[i], scale); -} - - -/* exported interface documented in netsurf/browser_window.h */ -void browser_window_set_scale(struct browser_window *bw, float scale, bool all) -{ - while (bw->parent && all) - bw = bw->parent; - - browser_window_set_scale_internal(bw, scale); - - if (bw->parent) - bw = bw->parent; - - browser_window_recalculate_frameset(bw); -} - - -/* exported interface documented in netsurf/browser_window.h */ -float browser_window_get_scale(struct browser_window *bw) -{ - if (bw == NULL) { - return 1.0; - } - - return bw->scale; -} - -/** - * Find browser window. - * - * \param bw Browser window. - * \param target Name of target. - * \param depth Depth to scan. - * \param page The browser window page. - * \param rdepth The rdepth. - * \param bw_target the output browser window. - */ -static void browser_window_find_target_internal(struct browser_window *bw, - const char *target, int depth, struct browser_window *page, - int *rdepth, struct browser_window **bw_target) -{ - int i; - - if ((bw->name) && (!strcasecmp(bw->name, target))) { - if ((bw == page) || (depth > *rdepth)) { - *rdepth = depth; - *bw_target = bw; - } - } - - if ((!bw->children) && (!bw->iframes)) - return; - - depth++; - - if (bw->children != NULL) { - for (i = 0; i < (bw->cols * bw->rows); i++) { - if ((bw->children[i].name) && - (!strcasecmp(bw->children[i].name, - target))) { - if ((page == &bw->children[i]) || - (depth > *rdepth)) { - *rdepth = depth; - *bw_target = &bw->children[i]; - } - } - if (bw->children[i].children) - browser_window_find_target_internal( - &bw->children[i], - target, depth, page, - rdepth, bw_target); - } - } - - if (bw->iframes != NULL) { - for (i = 0; i < bw->iframe_count; i++) - browser_window_find_target_internal(&bw->iframes[i], - target, depth, page, rdepth, bw_target); - } -} - - -/* exported interface documented in netsurf/browser_window.h */ -struct browser_window *browser_window_find_target(struct browser_window *bw, - const char *target, browser_mouse_state mouse) -{ - struct browser_window *bw_target; - struct browser_window *top; - hlcache_handle *c; - int rdepth; - nserror error; - - /* use the base target if we don't have one */ - c = bw->current_content; - if (target == NULL && c != NULL && content_get_type(c) == CONTENT_HTML) - target = html_get_base_target(c); - if (target == NULL) - target = TARGET_SELF; - - /* allow the simple case of target="_blank" to be ignored if requested - */ - if ((!(mouse & BROWSER_MOUSE_CLICK_2)) && - (!((mouse & BROWSER_MOUSE_CLICK_2) && - (mouse & BROWSER_MOUSE_MOD_2))) && - (!nsoption_bool(target_blank))) { - /* not a mouse button 2 click - * not a mouse button 1 click with ctrl pressed - * configured to ignore target="_blank" */ - if ((target == TARGET_BLANK) || (!strcasecmp(target, "_blank"))) - return bw; - } - - /* handle reserved keywords */ - if (((nsoption_bool(button_2_tab)) && - (mouse & BROWSER_MOUSE_CLICK_2))|| - ((!nsoption_bool(button_2_tab)) && - ((mouse & BROWSER_MOUSE_CLICK_1) && - (mouse & BROWSER_MOUSE_MOD_2))) || - ((nsoption_bool(button_2_tab)) && - ((target == TARGET_BLANK) || - (!strcasecmp(target, "_blank"))))) { - /* open in new tab if: - * - button_2 opens in new tab and button_2 was pressed - * OR - * - button_2 doesn't open in new tabs and button_1 was - * pressed with ctrl held - * OR - * - button_2 opens in new tab and the link target is "_blank" - */ - error = browser_window_create(BW_CREATE_TAB | - BW_CREATE_HISTORY | - BW_CREATE_CLONE, - NULL, - NULL, - bw, - &bw_target); - if (error != NSERROR_OK) { - return bw; - } - return bw_target; - } else if (((!nsoption_bool(button_2_tab)) && - (mouse & BROWSER_MOUSE_CLICK_2)) || - ((nsoption_bool(button_2_tab)) && - ((mouse & BROWSER_MOUSE_CLICK_1) && - (mouse & BROWSER_MOUSE_MOD_2))) || - ((!nsoption_bool(button_2_tab)) && - ((target == TARGET_BLANK) || - (!strcasecmp(target, "_blank"))))) { - /* open in new window if: - * - button_2 doesn't open in new tabs and button_2 was pressed - * OR - * - button_2 opens in new tab and button_1 was pressed with - * ctrl held - * OR - * - button_2 doesn't open in new tabs and the link target is - * "_blank" - */ - error = browser_window_create(BW_CREATE_HISTORY | - BW_CREATE_CLONE, - NULL, - NULL, - bw, - &bw_target); - if (error != NSERROR_OK) { - return bw; - } - return bw_target; - } else if ((target == TARGET_SELF) || (!strcasecmp(target, "_self"))) { - return bw; - } else if ((target == TARGET_PARENT) || - (!strcasecmp(target, "_parent"))) { - if (bw->parent) - return bw->parent; - return bw; - } else if ((target == TARGET_TOP) || (!strcasecmp(target, "_top"))) { - while (bw->parent) - bw = bw->parent; - return bw; - } - - /* find frame according to B.8, ie using the following priorities: - * - * 1) current frame - * 2) closest to front - */ - rdepth = -1; - bw_target = NULL; - for (top = bw; top->parent; top = top->parent); - browser_window_find_target_internal(top, target, 0, bw, &rdepth, - &bw_target); - if (bw_target) - return bw_target; - - /* we require a new window using the target name */ - if (!nsoption_bool(target_blank)) - return bw; - - error = browser_window_create(BW_CREATE_CLONE | BW_CREATE_HISTORY, - NULL, - NULL, - bw, - &bw_target); - if (error != NSERROR_OK) { - return bw; - } - - /* frame names should begin with an alphabetic character (a-z,A-Z), - * however in practice you get things such as '_new' and '2left'. The - * only real effect this has is when giving out names as it can be - * assumed that an author intended '_new' to create a new nameless - * window (ie '_blank') whereas in the case of '2left' the intention - * was for a new named window. As such we merely special case windows - * that begin with an underscore. */ - if (target[0] != '_') { - bw_target->name = strdup(target); - if (!bw_target->name) - guit->misc->warning("NoMemory", NULL); - } - return bw_target; -} - - -/** - * Handles the end of a drag operation in a browser window. - * - * \param bw browser window - * \param mouse state of mouse buttons and modifier keys - * \param x coordinate of mouse - * \param y coordinate of mouse - * - * \todo Remove this function, once these things are associated with content, - * rather than bw. - */ -static void browser_window_mouse_drag_end(struct browser_window *bw, - browser_mouse_state mouse, int x, int y) -{ - int scr_x, scr_y; - - switch (bw->drag.type) { - case DRAGGING_SELECTION: - case DRAGGING_OTHER: - case DRAGGING_CONTENT_SCROLLBAR: - /* Drag handled by content handler */ - break; - - case DRAGGING_SCR_X: - - browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y); - - scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); - scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); - - scrollbar_mouse_drag_end(bw->scroll_x, mouse, scr_x, scr_y); - - bw->drag.type = DRAGGING_NONE; - break; - - case DRAGGING_SCR_Y: - - browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y); - - scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); - scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); - - scrollbar_mouse_drag_end(bw->scroll_y, mouse, scr_x, scr_y); - - bw->drag.type = DRAGGING_NONE; - break; - - default: - browser_window_set_drag_type(bw, DRAGGING_NONE, NULL); - break; - } -} - - -/* exported interface documented in netsurf/browser_window.h */ -void browser_window_mouse_track(struct browser_window *bw, - browser_mouse_state mouse, int x, int y) -{ - hlcache_handle *c = bw->current_content; - const char *status = NULL; - browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; - - if (bw->window != NULL && bw->drag.window && bw != bw->drag.window) { - /* This is the root browser window and there's an active drag - * in a sub window. - * Pass the mouse action straight on to that bw. */ - struct browser_window *drag_bw = bw->drag.window; - int off_x = 0; - int off_y = 0; - - browser_window_get_position(drag_bw, true, &off_x, &off_y); - - if (drag_bw->browser_window_type == BROWSER_WINDOW_FRAME) { - browser_window_mouse_track(drag_bw, mouse, - x - off_x, y - off_y); - - } else if (drag_bw->browser_window_type == - BROWSER_WINDOW_IFRAME) { - browser_window_mouse_track(drag_bw, mouse, - x - off_x / bw->scale, - y - off_y / bw->scale); - } - return; - } - - if (bw->children) { - /* Browser window has children (frames) */ - struct browser_window *child; - int cur_child; - int children = bw->rows * bw->cols; - - for (cur_child = 0; cur_child < children; cur_child++) { - - child = &bw->children[cur_child]; - - if (x < child->x || y < child->y || - child->x + child->width < x || - child->y + child->height < y) { - /* Click not in this child */ - continue; - } - - /* It's this child that contains the mouse; pass - * mouse action on to child */ - browser_window_mouse_track(child, mouse, - x - child->x + scrollbar_get_offset( - child->scroll_x), - y - child->y + scrollbar_get_offset( - child->scroll_y)); - - /* Mouse action was for this child, we're done */ - return; - } - - /* Odd if we reached here, but nothing else can use the click - * when there are children. */ - return; - } - - if (c == NULL && bw->drag.type != DRAGGING_FRAME) { - return; - } - - if (bw->drag.type != DRAGGING_NONE && !mouse) { - browser_window_mouse_drag_end(bw, mouse, x, y); - } - - /* Browser window's horizontal scrollbar */ - if (bw->scroll_x != NULL && bw->drag.type != DRAGGING_SCR_Y) { - int scr_x, scr_y; - browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y); - scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); - scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); - - if ((bw->drag.type == DRAGGING_SCR_X) || - (scr_x > 0 && - scr_x < browser_window_get_scrollbar_len(bw, true) && - scr_y > 0 && - scr_y < SCROLLBAR_WIDTH && - bw->drag.type == DRAGGING_NONE)) { - /* Start a scrollbar drag, or continue existing drag */ - status = scrollbar_mouse_status_to_message( - scrollbar_mouse_action( - bw->scroll_x, mouse, - scr_x, scr_y)); - pointer = BROWSER_POINTER_DEFAULT; - - if (status != NULL) { - browser_window_set_status(bw, status); - } - - browser_window_set_pointer(bw, pointer); - return; - } - } - - /* Browser window's vertical scrollbar */ - if (bw->scroll_y != NULL) { - int scr_x, scr_y; - browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y); - scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); - scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); - - if ((bw->drag.type == DRAGGING_SCR_Y) || - (scr_y > 0 && - scr_y < browser_window_get_scrollbar_len(bw, false) && - scr_x > 0 && - scr_x < SCROLLBAR_WIDTH && - bw->drag.type == DRAGGING_NONE)) { - /* Start a scrollbar drag, or continue existing drag */ - status = scrollbar_mouse_status_to_message( - scrollbar_mouse_action( - bw->scroll_y, mouse, - scr_x, scr_y)); - pointer = BROWSER_POINTER_DEFAULT; - - if (status != NULL) { - browser_window_set_status(bw, status); - } - - browser_window_set_pointer(bw, pointer); - return; - } - } - - if (bw->drag.type == DRAGGING_FRAME) { - browser_window_resize_frame(bw, bw->x + x, bw->y + y); - } else if (bw->drag.type == DRAGGING_PAGE_SCROLL) { - /* mouse movement since drag started */ - struct rect rect; - - rect.x0 = bw->drag.start_x - x; - rect.y0 = bw->drag.start_y - y; - - /* new scroll offsets */ - rect.x0 += bw->drag.start_scroll_x; - rect.y0 += bw->drag.start_scroll_y; - - bw->drag.start_scroll_x = rect.x1 = rect.x0; - bw->drag.start_scroll_y = rect.y1 = rect.y0; - - browser_window_set_scroll(bw, &rect); - } else { - assert(c != NULL); - content_mouse_track(c, bw, mouse, x, y); - } -} - - -/* exported interface documented in netsurf/browser_window.h */ -void browser_window_mouse_click(struct browser_window *bw, - browser_mouse_state mouse, int x, int y) -{ - hlcache_handle *c = bw->current_content; - const char *status = NULL; - browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; - - if (bw->children) { - /* Browser window has children (frames) */ - struct browser_window *child; - int cur_child; - int children = bw->rows * bw->cols; - - for (cur_child = 0; cur_child < children; cur_child++) { - - child = &bw->children[cur_child]; - - if (x < child->x || y < child->y || - child->x + child->width < x || - child->y + child->height < y) { - /* Click not in this child */ - continue; - } - - /* It's this child that contains the click; pass it - * on to child. */ - browser_window_mouse_click(child, mouse, - x - child->x + scrollbar_get_offset( - child->scroll_x), - y - child->y + scrollbar_get_offset( - child->scroll_y)); - - /* Mouse action was for this child, we're done */ - return; - } - - return; - } - - if (!c) - return; - - if (bw->scroll_x != NULL) { - int scr_x, scr_y; - browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y); - scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); - scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); - - if (scr_x > 0 && scr_x < browser_window_get_scrollbar_len(bw, - true) && - scr_y > 0 && scr_y < SCROLLBAR_WIDTH) { - status = scrollbar_mouse_status_to_message( - scrollbar_mouse_action( - bw->scroll_x, mouse, - scr_x, scr_y)); - pointer = BROWSER_POINTER_DEFAULT; - - if (status != NULL) - browser_window_set_status(bw, status); - - browser_window_set_pointer(bw, pointer); - return; - } - } - - if (bw->scroll_y != NULL) { - int scr_x, scr_y; - browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y); - scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); - scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); - - if (scr_y > 0 && scr_y < browser_window_get_scrollbar_len(bw, - false) && - scr_x > 0 && scr_x < SCROLLBAR_WIDTH) { - status = scrollbar_mouse_status_to_message( - scrollbar_mouse_action( - bw->scroll_y, mouse, - scr_x, scr_y)); - pointer = BROWSER_POINTER_DEFAULT; - - if (status != NULL) - browser_window_set_status(bw, status); - - browser_window_set_pointer(bw, pointer); - return; - } - } - - switch (content_get_type(c)) { - case CONTENT_HTML: - case CONTENT_TEXTPLAIN: - { - /* Give bw focus */ - struct browser_window *root_bw = browser_window_get_root(bw); - if (bw != root_bw->focus) { - browser_window_remove_caret(bw, false); - browser_window_set_selection(bw, false, true); - root_bw->focus = bw; - } - - /* Pass mouse action to content */ - content_mouse_action(c, bw, mouse, x, y); - } - break; - default: - if (mouse & BROWSER_MOUSE_MOD_2) { - if (mouse & BROWSER_MOUSE_DRAG_2) { - guit->window->drag_save_object(bw->window, c, - GUI_SAVE_OBJECT_NATIVE); - } else if (mouse & BROWSER_MOUSE_DRAG_1) { - guit->window->drag_save_object(bw->window, c, - GUI_SAVE_OBJECT_ORIG); - } - } else if (mouse & (BROWSER_MOUSE_DRAG_1 | - BROWSER_MOUSE_DRAG_2)) { - browser_window_page_drag_start(bw, x, y); - browser_window_set_pointer(bw, BROWSER_POINTER_MOVE); - } - break; - } -} - - - -/* exported interface documented in netsurf/browser_window.h */ -void browser_window_redraw_rect(struct browser_window *bw, int x, int y, - int width, int height) -{ - content_request_redraw(bw->current_content, x, y, width, height); -} - - -/* exported interface documented in netsurf/browser_window.h */ -void browser_window_page_drag_start(struct browser_window *bw, int x, int y) -{ - assert(bw != NULL); - - browser_window_set_drag_type(bw, DRAGGING_PAGE_SCROLL, NULL); - - bw->drag.start_x = x; - bw->drag.start_y = y; - - if (bw->window != NULL) { - /* Front end window */ - guit->window->get_scroll(bw->window, - &bw->drag.start_scroll_x, - &bw->drag.start_scroll_y); - - guit->window->scroll_start(bw->window); - } else { - /* Core managed browser window */ - bw->drag.start_scroll_x = scrollbar_get_offset(bw->scroll_x); - bw->drag.start_scroll_y = scrollbar_get_offset(bw->scroll_y); - } -} - - - -/* exported interface documented in netsurf/browser_window.h */ -bool browser_window_back_available(struct browser_window *bw) -{ - return (bw && bw->history && - browser_window_history_back_available(bw)); -} - - - -/* exported interface documented in netsurf/browser_window.h */ -bool browser_window_forward_available(struct browser_window *bw) -{ - return (bw && bw->history && - browser_window_history_forward_available(bw)); -} - -/* exported interface documented in netsurf/browser_window.h */ -bool browser_window_reload_available(struct browser_window *bw) -{ - return (bw && bw->current_content && !bw->loading_content); -} - - -/* exported interface documented in netsurf/browser_window.h */ -bool browser_window_stop_available(struct browser_window *bw) -{ - return (bw && (bw->loading_content || - (bw->current_content && - (content_get_status(bw->current_content) != - CONTENT_STATUS_DONE)))); -} - -/* exported interface documented in browser.h */ +/* exported interface documented in netsurf/browser.h */ nserror browser_set_dpi(int dpi) { + if (dpi < 72 || dpi > 250) { + int bad = dpi; + dpi = min(max(dpi, 72), 250); + NSLOG(netsurf, INFO, "Clamping invalid DPI %d to %d", bad, dpi); + } nscss_screen_dpi = INTTOFIX(dpi); return NSERROR_OK; } -/* exported interface documented in browser.h */ +/* exported interface documented in netsurf/browser.h */ int browser_get_dpi(void) { return FIXTOINT(nscss_screen_dpi); diff --git a/desktop/browser_history.c b/desktop/browser_history.c index 640302773..ce9821af8 100644 --- a/desktop/browser_history.c +++ b/desktop/browser_history.c @@ -33,19 +33,16 @@ #include "netsurf/layout.h" #include "netsurf/content.h" #include "netsurf/window.h" +#include "netsurf/browser_window.h" #include "content/hlcache.h" #include "content/urldb.h" #include "netsurf/bitmap.h" +#include "utils/corestrings.h" #include "desktop/gui_internal.h" -#include "desktop/browser_history.h" #include "desktop/browser_private.h" - -#define WIDTH 100 -#define HEIGHT 86 -#define RIGHT_MARGIN 50 -#define BOTTOM_MARGIN 30 - +#include "desktop/local_history_private.h" +#include "desktop/browser_history.h" /** * Clone a history entry @@ -106,8 +103,10 @@ browser_window_history__clone_entry(struct history *history, unsigned char *bmdst_data; size_t bmsize; - new_entry->page.bitmap = guit->bitmap->create(WIDTH, HEIGHT, - BITMAP_NEW | BITMAP_OPAQUE); + new_entry->page.bitmap = guit->bitmap->create( + LOCAL_HISTORY_WIDTH, + LOCAL_HISTORY_HEIGHT, + BITMAP_OPAQUE); if (new_entry->page.bitmap != NULL) { bmsrc_data = guit->bitmap->get_buffer(entry->page.bitmap); @@ -201,26 +200,26 @@ static int browser_window_history__layout_subtree(struct history *history, struct history_entry *child; int y1 = y; - if (history->width < x + WIDTH) - history->width = x + WIDTH; + if (history->width < x + LOCAL_HISTORY_WIDTH) + history->width = x + LOCAL_HISTORY_WIDTH; if (!entry->forward) { entry->x = x; entry->y = y; - return y + HEIGHT; + return y + LOCAL_HISTORY_HEIGHT; } /* layout child subtrees below each other */ for (child = entry->forward; child; child = child->next) { y1 = browser_window_history__layout_subtree(history, child, - x + WIDTH + RIGHT_MARGIN, y1); + x + LOCAL_HISTORY_WIDTH + LOCAL_HISTORY_RIGHT_MARGIN, y1); if (child->next) - y1 += BOTTOM_MARGIN; + y1 += LOCAL_HISTORY_BOTTOM_MARGIN; } /* place ourselves in the middle */ entry->x = x; - entry->y = (y + y1) / 2 - HEIGHT / 2; + entry->y = (y + y1) / 2 - LOCAL_HISTORY_HEIGHT / 2; return y1; } @@ -243,12 +242,13 @@ static void browser_window_history__layout(struct history *history) if (history->start) history->height = browser_window_history__layout_subtree( history, history->start, - RIGHT_MARGIN / 2, BOTTOM_MARGIN / 2); + LOCAL_HISTORY_RIGHT_MARGIN / 2, + LOCAL_HISTORY_BOTTOM_MARGIN / 2); else history->height = 0; - history->width += RIGHT_MARGIN / 2; - history->height += BOTTOM_MARGIN / 2; + history->width += LOCAL_HISTORY_RIGHT_MARGIN / 2; + history->height += LOCAL_HISTORY_BOTTOM_MARGIN / 2; } @@ -274,7 +274,8 @@ static bool browser_window_history__enumerate_entry( const struct history_entry *child; if (!cb(bw, entry->x, entry->y, - entry->x + WIDTH, entry->y + HEIGHT, + entry->x + LOCAL_HISTORY_WIDTH, + entry->y + LOCAL_HISTORY_HEIGHT, entry, ud)) return false; @@ -303,8 +304,8 @@ nserror browser_window_history_create(struct browser_window *bw) return NSERROR_NOMEM; } - history->width = RIGHT_MARGIN / 2; - history->height = BOTTOM_MARGIN / 2; + history->width = LOCAL_HISTORY_RIGHT_MARGIN / 2; + history->height = LOCAL_HISTORY_BOTTOM_MARGIN / 2; bw->history = history; @@ -385,8 +386,9 @@ browser_window_history_add(struct browser_window *bw, NSLOG(netsurf, DEBUG, "Creating thumbnail for %s", nsurl_access(entry->page.url)); - entry->page.bitmap = guit->bitmap->create(WIDTH, HEIGHT, - BITMAP_NEW | BITMAP_CLEAR_MEMORY | BITMAP_OPAQUE); + entry->page.bitmap = guit->bitmap->create( + LOCAL_HISTORY_WIDTH, LOCAL_HISTORY_HEIGHT, + BITMAP_CLEAR | BITMAP_OPAQUE); if (entry->page.bitmap != NULL) { ret = guit->bitmap->render(entry->page.bitmap, content); if (ret != NSERROR_OK) { @@ -433,9 +435,7 @@ nserror browser_window_history_update(struct browser_window *bw, history = bw->history; - if (!history || - !history->current || - !history->current->page.bitmap) { + if ((history == NULL) || (history->current == NULL)) { return NSERROR_INVALID; } @@ -454,13 +454,22 @@ nserror browser_window_history_update(struct browser_window *bw, guit->bitmap->render(history->current->page.bitmap, content); } - if (bw->window != NULL && + if ((bw->window != NULL) && guit->window->get_scroll(bw->window, &sx, &sy)) { + int content_height = content_get_height(content); + int content_width = content_get_width(content); + /* clamp width and height values */ + if (content_height < 1) { + content_height = 1; + } + if (content_width < 1) { + content_width = 1; + } /* Successfully got scroll offsets, update the entry */ history->current->page.scroll_x = \ - (float)sx / (float)content_get_width(content); + (float)sx / (float)content_width; history->current->page.scroll_y = \ - (float)sy / (float)content_get_height(content); + (float)sy / (float)content_height; NSLOG(netsurf, INFO, "Updated scroll offsets to %g by %g", history->current->page.scroll_x, history->current->page.scroll_y); @@ -479,9 +488,7 @@ browser_window_history_get_scroll(struct browser_window *bw, history = bw->history; - if (!history || - !history->current || - !history->current->page.bitmap) { + if ((history== NULL) || (history->current == NULL)) { return NSERROR_INVALID; } @@ -510,6 +517,24 @@ void browser_window_history_destroy(struct browser_window *bw) /* exported interface documented in desktop/browser_history.h */ nserror browser_window_history_back(struct browser_window *bw, bool new_window) { + if (bw != NULL && bw->internal_nav) { + /* All internal nav back operations ignore new_window */ + if (bw->current_parameters.url != NULL) { + /* There are some internal parameters, restart from there */ + return browser_window__reload_current_parameters(bw); + } else { + /* No internal parameters, just navigate to about:blank */ + return browser_window_navigate( + bw, + corestring_nsurl_about_blank, + NULL, /* Referer */ + BW_NAVIGATE_HISTORY, + NULL, /* Post */ + NULL, /* Post */ + NULL /* parent fetch */); + } + } + if (!bw || !bw->history || !bw->history->current || !bw->history->current->back) { return NSERROR_BAD_PARAMETER; @@ -601,7 +626,9 @@ nserror browser_window_history_go(struct browser_window *bw, url, NULL, bw, NULL); history->current = current; } else { - browser_window_history_update(bw, bw->current_content); + if (bw->current_content != NULL) { + browser_window_history_update(bw, bw->current_content); + } history->current = entry; error = browser_window_navigate(bw, url, NULL, BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE, @@ -625,7 +652,8 @@ void browser_window_history_enumerate_forward(const struct browser_window *bw, e = bw->history->current->forward_pref; for (; e != NULL; e = e->forward_pref) { - if (!cb(bw, e->x, e->y, e->x + WIDTH, e->y + HEIGHT, + if (!cb(bw, e->x, e->y, e->x + LOCAL_HISTORY_WIDTH, + e->y + LOCAL_HISTORY_HEIGHT, e, user_data)) break; } @@ -642,7 +670,8 @@ void browser_window_history_enumerate_back(const struct browser_window *bw, return; for (e = bw->history->current->back; e != NULL; e = e->back) { - if (!cb(bw, e->x, e->y, e->x + WIDTH, e->y + HEIGHT, + if (!cb(bw, e->x, e->y, e->x + LOCAL_HISTORY_WIDTH, + e->y + LOCAL_HISTORY_HEIGHT, e, user_data)) break; } diff --git a/desktop/browser_private.h b/desktop/browser_private.h index 192d22bf0..40c3b43ce 100644 --- a/desktop/browser_private.h +++ b/desktop/browser_private.h @@ -22,14 +22,10 @@ * Browser window private structure. */ -#ifndef _NETSURF_DESKTOP_BROWSER_PRIVATE_H_ -#define _NETSURF_DESKTOP_BROWSER_PRIVATE_H_ - -#include <libwapcaplet/libwapcaplet.h> - -#include "netsurf/types.h" -#include "netsurf/browser_window.h" +#ifndef NETSURF_DESKTOP_BROWSER_PRIVATE_H_ +#define NETSURF_DESKTOP_BROWSER_PRIVATE_H_ +#include "content/fetch.h" #include "desktop/frame_types.h" struct box; @@ -81,6 +77,20 @@ struct history { }; /** + * The parameters for a fetch. + */ +struct browser_fetch_parameters { + struct nsurl *url; /**< The URL to fetch */ + struct nsurl *referrer; /**< Optional refererer */ + enum browser_window_nav_flags flags; /**< Navigation flags */ + char *post_urlenc; /**< URL encoded post data */ + struct fetch_multipart_data *post_multipart; /**< Multipart post data */ + char *parent_charset; /**< Optional parent character set */ + bool parent_quirks; /**< Optional parent quirks */ +}; + + +/** * Browser window data. */ struct browser_window { @@ -89,6 +99,17 @@ struct browser_window { * READY or DONE status or NULL for no content. */ struct hlcache_handle *current_content; + + /** + * The fetch parameters for the current content + */ + struct browser_fetch_parameters current_parameters; + + /** + * The certificate chain for the current content + */ + struct cert_chain *current_cert_chain; + /** * Content handle of page in process of being loaded or NULL * if no page is being loaded. @@ -96,6 +117,16 @@ struct browser_window { struct hlcache_handle *loading_content; /** + * The fetch parameters for the loading content + */ + struct browser_fetch_parameters loading_parameters; + + /** + * The certificate chain for the loading content + */ + struct cert_chain *loading_cert_chain; + + /** * Favicon */ struct { @@ -129,6 +160,8 @@ struct browser_window { bool throbbing; /** Add loading_content to the window history when it loads. */ bool history_add; + /** Internal navigation, do not update URL etc */ + bool internal_nav; /** Fragment identifier for current_content. */ lwc_string *frag_id; @@ -230,7 +263,7 @@ struct browser_window { bool can_edit; /** current javascript context */ - struct jscontext *jsctx; + struct jsheap *jsheap; /** cache of the currently displayed status text. */ struct { @@ -250,19 +283,27 @@ struct browser_window { * \param existing The existing window if cloning, else NULL */ nserror browser_window_initialise_common(enum browser_window_create_flags flags, - struct browser_window *bw, struct browser_window *existing); + struct browser_window *bw, + const struct browser_window *existing); /** + * Release all memory associated with a browser window. + * + * \param bw browser window + */ +nserror browser_window_destroy_internal(struct browser_window *bw); + +/** * Get the dimensions of the area a browser window occupies * * \param bw The browser window to get dimensions of * \param width Updated to the browser window viewport width * \param height Updated to the browser window viewport height - * \param scaled Whether we want the height with scale applied + * \return NSERROR_OK and width and height updated otherwise error code */ -void browser_window_get_dimensions(struct browser_window *bw, - int *width, int *height, bool scaled); +nserror browser_window_get_dimensions(struct browser_window *bw, + int *width, int *height); /** @@ -275,6 +316,15 @@ void browser_window_update_extent(struct browser_window *bw); /** + * Cause an area of a browser window to be marked invalid and hence redrawn. + * + * \param bw The browser window to update. + * \param rect The area to redraw + */ +nserror browser_window_invalidate_rect(struct browser_window *bw, struct rect *rect); + + +/** * Change the status bar of a browser window. * * \param bw browser window @@ -289,7 +339,8 @@ void browser_window_set_status(struct browser_window *bw, const char *text); * \param bw browser window to set the type of the current drag for * \return root browser window */ -struct browser_window * browser_window_get_root(struct browser_window *bw); +struct browser_window * browser_window_get_root( + struct browser_window *bw); /** @@ -354,5 +405,16 @@ nserror browser_window_history_get_scroll(struct browser_window *bw, */ void browser_window_history_destroy(struct browser_window *bw); +/** + * Type for handling query responses short-term + */ +typedef nserror (*browser_window_query_callback)(bool proceed, void *pw); + +/** + * Navigate a browser window to the current parameters + * + * \param bw The browser window to cause to navigate + */ +nserror browser_window__reload_current_parameters(struct browser_window *bw); #endif diff --git a/desktop/browser_window.c b/desktop/browser_window.c new file mode 100644 index 000000000..55366a03e --- /dev/null +++ b/desktop/browser_window.c @@ -0,0 +1,4803 @@ +/* + * Copyright 2008 Michael Drake <tlsa@netsurf-browser.org> + * Copyright 2010 Daniel Silverstone <dsilvers@digital-scurf.org> + * Copyright 2010-2020 Vincent Sanders <vince@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * + * Browser window creation and manipulation implementation. + */ + +#include "utils/config.h" + +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <nsutils/time.h> + +#include "utils/errors.h" +#include "utils/log.h" +#include "utils/corestrings.h" +#include "utils/messages.h" +#include "utils/nsoption.h" +#include "netsurf/types.h" +#include "netsurf/browser_window.h" +#include "netsurf/window.h" +#include "netsurf/misc.h" +#include "netsurf/content.h" +#include "netsurf/search.h" +#include "netsurf/plotters.h" +#include "content/content.h" +#include "content/hlcache.h" +#include "content/urldb.h" +#include "content/content_debug.h" + +#include "html/html.h" +#include "html/form_internal.h" +#include "javascript/js.h" + +#include "desktop/browser_private.h" +#include "desktop/scrollbar.h" +#include "desktop/gui_internal.h" +#include "desktop/download.h" +#include "desktop/frames.h" +#include "desktop/global_history.h" +#include "desktop/textinput.h" +#include "desktop/hotlist.h" +#include "desktop/knockout.h" +#include "desktop/browser_history.h" +#include "desktop/theme.h" + +#ifdef WITH_THEME_INSTALL +#include "desktop/theme.h" +#endif + +/** + * smallest scale that can be applied to a browser window + */ +#define SCALE_MINIMUM 0.2 + +/** + * largests scale that can be applied to a browser window + */ +#define SCALE_MAXIMUM 10.0 + +/** + * maximum frame depth + */ +#define FRAME_DEPTH 8 + +/* Forward declare internal navigation function */ +static nserror browser_window__navigate_internal( + struct browser_window *bw, struct browser_fetch_parameters *params); + + +/** + * Close and destroy all child browser window. + * + * \param bw browser window + */ +static void browser_window_destroy_children(struct browser_window *bw) +{ + int i; + + if (bw->children) { + for (i = 0; i < (bw->rows * bw->cols); i++) { + browser_window_destroy_internal(&bw->children[i]); + } + free(bw->children); + bw->children = NULL; + bw->rows = 0; + bw->cols = 0; + } +} + + +/** + * Free the stored fetch parameters + * + * \param bw The browser window + */ +static void +browser_window__free_fetch_parameters(struct browser_fetch_parameters *params) +{ + if (params->url != NULL) { + nsurl_unref(params->url); + params->url = NULL; + } + if (params->referrer != NULL) { + nsurl_unref(params->referrer); + params->referrer = NULL; + } + if (params->post_urlenc != NULL) { + free(params->post_urlenc); + params->post_urlenc = NULL; + } + if (params->post_multipart != NULL) { + fetch_multipart_data_destroy(params->post_multipart); + params->post_multipart = NULL; + } + if (params->parent_charset != NULL) { + free(params->parent_charset); + params->parent_charset = NULL; + } +} + + +/** + * Get position of scrollbar widget within browser window. + * + * \param bw The browser window + * \param horizontal Whether to get position of horizontal scrollbar + * \param x Updated to x-coord of top left of scrollbar widget + * \param y Updated to y-coord of top left of scrollbar widget + */ +static inline void +browser_window_get_scrollbar_pos(struct browser_window *bw, + bool horizontal, + int *x, int *y) +{ + if (horizontal) { + *x = 0; + *y = bw->height - SCROLLBAR_WIDTH; + } else { + *x = bw->width - SCROLLBAR_WIDTH; + *y = 0; + } +} + + +/** + * Get browser window horizontal scrollbar widget length + * + * \param bw The browser window + * \return the scrollbar's length + */ +static inline int get_horz_scrollbar_len(struct browser_window *bw) +{ + if (bw->scroll_y == NULL) { + return bw->width; + } + return bw->width - SCROLLBAR_WIDTH; +} + + +/** + * Get browser window vertical scrollbar widget length + * + * \param bw The browser window + * \return the scrollbar's length + */ +static inline int get_vert_scrollbar_len(struct browser_window *bw) +{ + return bw->height; +} + + +/** + * Set or remove a selection. + * + * \param bw browser window with selection + * \param selection true if bw has a selection, false if removing selection + * \param read_only true iff selection is read only (e.g. can't cut it) + */ +static void +browser_window_set_selection(struct browser_window *bw, + bool selection, + bool read_only) +{ + struct browser_window *top; + + assert(bw != NULL); + + top = browser_window_get_root(bw); + + assert(top != NULL); + + if (bw != top->selection.bw && + top->selection.bw != NULL && + top->selection.bw->current_content != NULL) { + /* clear old selection */ + content_clear_selection(top->selection.bw->current_content); + } + + if (selection) { + top->selection.bw = bw; + } else { + top->selection.bw = NULL; + } + + top->selection.read_only = read_only; +} + + +/** + * Set the scroll position of a browser window. + * + * scrolls the viewport to ensure the specified rectangle of the + * content is shown. + * + * \param bw window to scroll + * \param rect The rectangle to ensure is shown. + * \return NSERROR_OK on success or apropriate error code. + */ +static nserror +browser_window_set_scroll(struct browser_window *bw, const struct rect *rect) +{ + if (bw->window != NULL) { + return guit->window->set_scroll(bw->window, rect); + } + + if (bw->scroll_x != NULL) { + scrollbar_set(bw->scroll_x, rect->x0, false); + } + if (bw->scroll_y != NULL) { + scrollbar_set(bw->scroll_y, rect->y0, false); + } + + return NSERROR_OK; +} + + +/** + * Internal helper for getting the positional features + * + * \param[in] bw browser window to examine. + * \param[in] x x-coordinate of point of interest + * \param[in] y y-coordinate of point of interest + * \param[out] data Feature structure to update. + * \return NSERROR_OK or appropriate error code on faliure. + */ +static nserror +browser_window__get_contextual_content(struct browser_window *bw, + int x, int y, + struct browser_window_features *data) +{ + nserror ret = NSERROR_OK; + + /* Handle (i)frame scroll offset (core-managed browser windows only) */ + x += scrollbar_get_offset(bw->scroll_x); + y += scrollbar_get_offset(bw->scroll_y); + + if (bw->children) { + /* Browser window has children, so pass request on to + * appropriate child. + */ + struct browser_window *bwc; + int cur_child; + int children = bw->rows * bw->cols; + + /* Loop through all children of bw */ + for (cur_child = 0; cur_child < children; cur_child++) { + /* Set current child */ + bwc = &bw->children[cur_child]; + + /* Skip this frame if (x, y) coord lies outside */ + if ((x < bwc->x) || + (bwc->x + bwc->width < x) || + (y < bwc->y) || + (bwc->y + bwc->height < y)) { + continue; + } + + /* Pass request into this child */ + return browser_window__get_contextual_content(bwc, + (x - bwc->x), (y - bwc->y), data); + } + + /* Coordinate not contained by any frame */ + + } else if (bw->current_content != NULL) { + /* Pass request to content */ + ret = content_get_contextual_content(bw->current_content, + x, y, data); + data->main = bw->current_content; + } + + return ret; +} + + +/** + * implements the download operation of a window navigate + */ +static nserror +browser_window_download(struct browser_window *bw, + nsurl *url, + nsurl *nsref, + uint32_t fetch_flags, + bool fetch_is_post, + llcache_post_data *post) +{ + llcache_handle *l; + struct browser_window *root; + nserror error; + + root = browser_window_get_root(bw); + assert(root != NULL); + + fetch_flags |= LLCACHE_RETRIEVE_FORCE_FETCH; + fetch_flags |= LLCACHE_RETRIEVE_STREAM_DATA; + + error = llcache_handle_retrieve(url, fetch_flags, nsref, + fetch_is_post ? post : NULL, + NULL, NULL, &l); + if (error == NSERROR_NO_FETCH_HANDLER) { + /* no internal handler for this type, call out to frontend */ + error = guit->misc->launch_url(url); + } else if (error != NSERROR_OK) { + NSLOG(netsurf, INFO, "Failed to fetch download: %d", error); + } else { + error = download_context_create(l, root->window); + if (error != NSERROR_OK) { + NSLOG(netsurf, INFO, + "Failed creating download context: %d", error); + llcache_handle_abort(l); + llcache_handle_release(l); + } + } + + return error; +} + + +/** + * recursively check browser windows for activity + * + * \param bw browser window to start checking from. + */ +static bool browser_window_check_throbber(struct browser_window *bw) +{ + int children, index; + + if (bw->throbbing) + return true; + + if (bw->children) { + children = bw->rows * bw->cols; + for (index = 0; index < children; index++) { + if (browser_window_check_throbber(&bw->children[index])) + return true; + } + } + + if (bw->iframes) { + for (index = 0; index < bw->iframe_count; index++) { + if (browser_window_check_throbber(&bw->iframes[index])) + return true; + } + } + + return false; +} + + +/** + * Start the busy indicator. + * + * \param bw browser window + */ +static nserror browser_window_start_throbber(struct browser_window *bw) +{ + bw->throbbing = true; + + while (bw->parent) + bw = bw->parent; + + return guit->window->event(bw->window, GW_EVENT_START_THROBBER); +} + + +/** + * Stop the busy indicator. + * + * \param bw browser window + */ +static nserror browser_window_stop_throbber(struct browser_window *bw) +{ + nserror res = NSERROR_OK; + + bw->throbbing = false; + + while (bw->parent) { + bw = bw->parent; + } + + if (!browser_window_check_throbber(bw)) { + res = guit->window->event(bw->window, GW_EVENT_STOP_THROBBER); + } + return res; +} + + +/** + * Callback for fetchcache() for browser window favicon fetches. + * + * \param c content handle of favicon + * \param event The event to process + * \param pw a context containing the browser window + * \return NSERROR_OK on success else appropriate error code. + */ +static nserror +browser_window_favicon_callback(hlcache_handle *c, + const hlcache_event *event, + void *pw) +{ + struct browser_window *bw = pw; + + switch (event->type) { + case CONTENT_MSG_DONE: + if (bw->favicon.current != NULL) { + content_close(bw->favicon.current); + hlcache_handle_release(bw->favicon.current); + } + + bw->favicon.current = c; + bw->favicon.loading = NULL; + + /* content_get_bitmap on the hlcache_handle should give + * the favicon bitmap at this point + */ + guit->window->set_icon(bw->window, c); + break; + + case CONTENT_MSG_ERROR: + + /* clean up after ourselves */ + if (c == bw->favicon.loading) { + bw->favicon.loading = NULL; + } else if (c == bw->favicon.current) { + bw->favicon.current = NULL; + } + + hlcache_handle_release(c); + + if (bw->favicon.failed == false) { + nsurl *nsref = NULL; + nsurl *nsurl; + nserror error; + + bw->favicon.failed = true; + + error = nsurl_create("resource:favicon.ico", &nsurl); + if (error != NSERROR_OK) { + NSLOG(netsurf, INFO, + "Unable to create default location url"); + } else { + hlcache_handle_retrieve(nsurl, + HLCACHE_RETRIEVE_SNIFF_TYPE, + nsref, NULL, + browser_window_favicon_callback, + bw, NULL, CONTENT_IMAGE, + &bw->favicon.loading); + + nsurl_unref(nsurl); + } + + } + break; + + default: + break; + + } + return NSERROR_OK; +} + + +/** + * update the favicon associated with the browser window + * + * \param c the page content handle. + * \param bw A top level browser window. + * \param link A link context or NULL to attempt fallback scanning. + */ +static nserror +browser_window_update_favicon(hlcache_handle *c, + struct browser_window *bw, + struct content_rfc5988_link *link) +{ + nsurl *nsref = NULL; + nsurl *nsurl; + nserror res; + + assert(c != NULL); + assert(bw !=NULL); + + if (bw->window == NULL) { + /* Not top-level browser window; not interested */ + return NSERROR_OK; + } + + /* already fetching the favicon - use that */ + if (bw->favicon.loading != NULL) { + return NSERROR_OK; + } + + bw->favicon.failed = false; + + if (link == NULL) { + /* Look for "icon" */ + link = content_find_rfc5988_link(c, corestring_lwc_icon); + } + + if (link == NULL) { + /* Look for "shortcut icon" */ + link = content_find_rfc5988_link(c, corestring_lwc_shortcut_icon); + } + + if (link == NULL) { + lwc_string *scheme; + bool speculative_default = false; + bool match; + + nsurl = hlcache_handle_get_url(c); + + scheme = nsurl_get_component(nsurl, NSURL_SCHEME); + + /* If the document was fetched over http(s), then speculate + * that there's a favicon living at /favicon.ico */ + if ((lwc_string_caseless_isequal(scheme, + corestring_lwc_http, + &match) == lwc_error_ok && + match) || + (lwc_string_caseless_isequal(scheme, + corestring_lwc_https, + &match) == lwc_error_ok && + match)) { + speculative_default = true; + } + + lwc_string_unref(scheme); + + if (speculative_default) { + /* no favicon via link, try for the default location */ + res = nsurl_join(nsurl, "/favicon.ico", &nsurl); + } else { + bw->favicon.failed = true; + res = nsurl_create("resource:favicon.ico", &nsurl); + } + if (res != NSERROR_OK) { + NSLOG(netsurf, INFO, + "Unable to create default location url"); + return res; + } + } else { + nsurl = nsurl_ref(link->href); + } + + if (link == NULL) { + NSLOG(netsurf, INFO, + "fetching general favicon from '%s'", + nsurl_access(nsurl)); + } else { + NSLOG(netsurf, INFO, + "fetching favicon rel:%s '%s'", + lwc_string_data(link->rel), + nsurl_access(nsurl)); + } + + res = hlcache_handle_retrieve(nsurl, + HLCACHE_RETRIEVE_SNIFF_TYPE, + nsref, + NULL, + browser_window_favicon_callback, + bw, + NULL, + CONTENT_IMAGE, + &bw->favicon.loading); + + nsurl_unref(nsurl); + + return res; +} + + +/** + * Handle meta http-equiv refresh time elapsing by loading a new page. + * + * \param p browser window to refresh with new page + */ +static void browser_window_refresh(void *p) +{ + struct browser_window *bw = p; + nsurl *url; + nsurl *refresh; + hlcache_handle *parent = NULL; + enum browser_window_nav_flags flags = BW_NAVIGATE_UNVERIFIABLE; + + assert(bw->current_content != NULL && + (content_get_status(bw->current_content) == + CONTENT_STATUS_READY || + content_get_status(bw->current_content) == + CONTENT_STATUS_DONE)); + + /* Ignore if the refresh URL has gone + * (may happen if a fetch error occurred) */ + refresh = content_get_refresh_url(bw->current_content); + if (refresh == NULL) + return; + + /* mark this content as invalid so it gets flushed from the cache */ + content_invalidate_reuse_data(bw->current_content); + + url = hlcache_handle_get_url(bw->current_content); + if ((url == NULL) || (nsurl_compare(url, refresh, NSURL_COMPLETE))) { + flags |= BW_NAVIGATE_HISTORY; + } + + /* Treat an (almost) immediate refresh in a top-level browser window as + * if it were an HTTP redirect, and thus make the resulting fetch + * verifiable. + * + * See fetchcache.c for why redirected fetches should be verifiable at + * all. + */ + if (bw->refresh_interval <= 100 && bw->parent == NULL) { + flags &= ~BW_NAVIGATE_UNVERIFIABLE; + } else { + parent = bw->current_content; + } + + browser_window_navigate(bw, + refresh, + url, + flags, + NULL, + NULL, + parent); + +} + + +/** + * Transfer the loading_content to a new download window. + */ +static void +browser_window_convert_to_download(struct browser_window *bw, + llcache_handle *stream) +{ + struct browser_window *root = browser_window_get_root(bw); + nserror error; + + assert(root != NULL); + + error = download_context_create(stream, root->window); + if (error != NSERROR_OK) { + llcache_handle_abort(stream); + llcache_handle_release(stream); + } + + /* remove content from browser window */ + hlcache_handle_release(bw->loading_content); + bw->loading_content = NULL; + + browser_window_stop_throbber(bw); +} + + +/** + * scroll to a fragment if present + * + * \param bw browser window + * \return true if the scroll was sucessful + */ +static bool frag_scroll(struct browser_window *bw) +{ + struct rect rect; + + if (bw->frag_id == NULL) { + return false; + } + + if (!html_get_id_offset(bw->current_content, + bw->frag_id, + &rect.x0, + &rect.y0)) { + return false; + } + + rect.x1 = rect.x0; + rect.y1 = rect.y0; + if (browser_window_set_scroll(bw, &rect) == NSERROR_OK) { + if (bw->current_content != NULL && + bw->history != NULL && + bw->history->current != NULL) { + browser_window_history_update(bw, bw->current_content); + } + return true; + } + return false; +} + + +/** + * Redraw browser window, set extent to content, and update title. + * + * \param bw browser_window + * \param scroll_to_top move view to top of page + */ +static void browser_window_update(struct browser_window *bw, bool scroll_to_top) +{ + static const struct rect zrect = { + .x0 = 0, + .y0 = 0, + .x1 = 0, + .y1 = 0 + }; + + if (bw->current_content == NULL) { + return; + } + + switch (bw->browser_window_type) { + + case BROWSER_WINDOW_NORMAL: + /* Root browser window, constituting a front end window/tab */ + guit->window->set_title(bw->window, + content_get_title(bw->current_content)); + + browser_window_update_extent(bw); + + /* if frag_id exists, then try to scroll to it */ + /** @todo don't do this if the user has scrolled */ + if (!frag_scroll(bw)) { + if (scroll_to_top) { + browser_window_set_scroll(bw, &zrect); + } + } + + guit->window->invalidate(bw->window, NULL); + + break; + + case BROWSER_WINDOW_IFRAME: + /* Internal iframe browser window */ + assert(bw->parent != NULL); + assert(bw->parent->current_content != NULL); + + browser_window_update_extent(bw); + + if (scroll_to_top) { + browser_window_set_scroll(bw, &zrect); + } + + /* if frag_id exists, then try to scroll to it */ + /** @todo don't do this if the user has scrolled */ + frag_scroll(bw); + + browser_window_invalidate_iframe(bw); + + break; + + case BROWSER_WINDOW_FRAME: + { + struct rect rect; + browser_window_update_extent(bw); + + if (scroll_to_top) { + browser_window_set_scroll(bw, &zrect); + } + + /* if frag_id exists, then try to scroll to it */ + /** @todo don't do this if the user has scrolled */ + frag_scroll(bw); + + rect.x0 = scrollbar_get_offset(bw->scroll_x); + rect.y0 = scrollbar_get_offset(bw->scroll_y); + rect.x1 = rect.x0 + bw->width; + rect.y1 = rect.y0 + bw->height; + + browser_window_invalidate_rect(bw, &rect); + } + break; + + default: + case BROWSER_WINDOW_FRAMESET: + /* Nothing to do */ + break; + } +} + + +/** + * handle message for content ready on browser window + */ +static nserror browser_window_content_ready(struct browser_window *bw) +{ + int width, height; + nserror res = NSERROR_OK; + + /* close and release the current window content */ + if (bw->current_content != NULL) { + content_close(bw->current_content); + hlcache_handle_release(bw->current_content); + } + + bw->current_content = bw->loading_content; + bw->loading_content = NULL; + + if (!bw->internal_nav) { + /* Transfer the fetch parameters */ + browser_window__free_fetch_parameters(&bw->current_parameters); + bw->current_parameters = bw->loading_parameters; + memset(&bw->loading_parameters, 0, sizeof(bw->loading_parameters)); + /* Transfer the certificate chain */ + cert_chain_free(bw->current_cert_chain); + bw->current_cert_chain = bw->loading_cert_chain; + bw->loading_cert_chain = NULL; + } + + /* Format the new content to the correct dimensions */ + browser_window_get_dimensions(bw, &width, &height); + width /= bw->scale; + height /= bw->scale; + content_reformat(bw->current_content, false, width, height); + + /* history */ + if (bw->history_add && bw->history && !bw->internal_nav) { + nsurl *url = hlcache_handle_get_url(bw->current_content); + + if (urldb_add_url(url)) { + urldb_set_url_title(url, content_get_title(bw->current_content)); + urldb_update_url_visit_data(url); + urldb_set_url_content_type(url, + content_get_type(bw->current_content)); + + /* This is safe as we've just added the URL */ + global_history_add(urldb_get_url(url)); + } + /** + * \todo Urldb / Thumbnails / Local history brokenness + * + * We add to local history after calling urldb_add_url rather + * than in the block above. If urldb_add_url fails (as it + * will for urls like "about:about", "about:config" etc), + * there would be no local history node, and later calls to + * history_update will either explode or overwrite the node + * for the previous URL. + * + * We call it after, rather than before urldb_add_url because + * history_add calls bitmap render, which tries to register + * the thumbnail with urldb. That thumbnail registration + * fails if the url doesn't exist in urldb already, and only + * urldb-registered thumbnails get freed. So if we called + * history_add before urldb_add_url we would leak thumbnails + * for all newly visited URLs. With the history_add call + * after, we only leak the thumbnails when urldb does not add + * the URL. + * + * Also, since browser_window_history_add can create a + * thumbnail (content_redraw), we need to do it after + * content_reformat. + */ + browser_window_history_add(bw, bw->current_content, bw->frag_id); + } + + browser_window_remove_caret(bw, false); + + if (bw->window != NULL) { + guit->window->event(bw->window, GW_EVENT_NEW_CONTENT); + + browser_window_refresh_url_bar(bw); + } + + /* new content; set scroll_to_top */ + browser_window_update(bw, true); + content_open(bw->current_content, bw, 0, 0); + browser_window_set_status(bw, content_get_status_message(bw->current_content)); + + /* frames */ + res = browser_window_create_frameset(bw); + + /* iframes */ + res = browser_window_create_iframes(bw); + + /* Indicate page status may have changed */ + if (res == NSERROR_OK) { + struct browser_window *root = browser_window_get_root(bw); + res = guit->window->event(root->window, GW_EVENT_PAGE_INFO_CHANGE); + } + + return res; +} + + +/** + * handle message for content done on browser window + */ +static nserror +browser_window_content_done(struct browser_window *bw) +{ + float sx, sy; + struct rect rect; + int scrollx; + int scrolly; + + if (bw->window == NULL) { + /* Updated browser window's scrollbars. */ + /** + * \todo update browser window scrollbars before CONTENT_MSG_DONE + */ + browser_window_reformat(bw, true, bw->width, bw->height); + browser_window_handle_scrollbars(bw); + } + + browser_window_update(bw, false); + browser_window_set_status(bw, content_get_status_message(bw->current_content)); + browser_window_stop_throbber(bw); + browser_window_update_favicon(bw->current_content, bw, NULL); + + if (browser_window_history_get_scroll(bw, &sx, &sy) == NSERROR_OK) { + scrollx = (int)((float)content_get_width(bw->current_content) * sx); + scrolly = (int)((float)content_get_height(bw->current_content) * sy); + rect.x0 = rect.x1 = scrollx; + rect.y0 = rect.y1 = scrolly; + if (browser_window_set_scroll(bw, &rect) != NSERROR_OK) { + NSLOG(netsurf, WARNING, + "Unable to set browser scroll offsets to %d by %d", + scrollx, scrolly); + } + } + + if (!bw->internal_nav) { + browser_window_history_update(bw, bw->current_content); + hotlist_update_url(hlcache_handle_get_url(bw->current_content)); + } + + if (bw->refresh_interval != -1) { + guit->misc->schedule(bw->refresh_interval * 10, + browser_window_refresh, bw); + } + + return NSERROR_OK; +} + + +/** + * Handle query responses from SSL requests + */ +static nserror +browser_window__handle_ssl_query_response(bool proceed, void *pw) +{ + struct browser_window *bw = (struct browser_window *)pw; + + /* If we're in the process of loading, stop the load */ + if (bw->loading_content != NULL) { + /* We had a loading content (maybe auth page?) */ + browser_window_stop(bw); + browser_window_remove_caret(bw, false); + browser_window_destroy_children(bw); + browser_window_destroy_iframes(bw); + } + + if (!proceed) { + /* We're processing a "back to safety", do a rough-and-ready + * nav to the old 'current' parameters, with any post data + * stripped away + */ + return browser_window__reload_current_parameters(bw); + } + + /* We're processing a "proceed" attempt from the form */ + /* First, we permit the SSL */ + urldb_set_cert_permissions(bw->loading_parameters.url, true); + + /* And then we navigate to the original loading parameters */ + bw->internal_nav = false; + + return browser_window__navigate_internal(bw, &bw->loading_parameters); +} + + +/** + * Unpack a "username:password" to components. + * + * \param[in] userpass The input string to split. + * \param[in] username_out Returns username on success. Owned by caller. + * \param[out] password_out Returns password on success. Owned by caller. + * \return NSERROR_OK, or appropriate error code. + */ +static nserror +browser_window__unpack_userpass(const char *userpass, + char **username_out, + char **password_out) +{ + const char *tmp; + char *username; + char *password; + size_t len; + + if (userpass == NULL) { + username = malloc(1); + password = malloc(1); + if (username == NULL || password == NULL) { + free(username); + free(password); + return NSERROR_NOMEM; + } + username[0] = '\0'; + password[0] = '\0'; + + *username_out = username; + *password_out = password; + return NSERROR_OK; + } + + tmp = strchr(userpass, ':'); + if (tmp == NULL) { + return NSERROR_BAD_PARAMETER; + } else { + size_t len2; + len = tmp - userpass; + len2 = strlen(++tmp); + + username = malloc(len + 1); + password = malloc(len2 + 1); + if (username == NULL || password == NULL) { + free(username); + free(password); + return NSERROR_NOMEM; + } + memcpy(username, userpass, len); + username[len] = '\0'; + memcpy(password, tmp, len2 + 1); + } + + *username_out = username; + *password_out = password; + return NSERROR_OK; +} + + +/** + * Build a "username:password" from components. + * + * \param[in] username The username component. + * \param[in] password The password component. + * \param[out] userpass_out Returns combined string on success. + * Owned by caller. + * \return NSERROR_OK, or appropriate error code. + */ +static nserror +browser_window__build_userpass(const char *username, + const char *password, + char **userpass_out) +{ + char *userpass; + size_t len; + + len = strlen(username) + 1 + strlen(password) + 1; + + userpass = malloc(len); + if (userpass == NULL) { + return NSERROR_NOMEM; + } + + snprintf(userpass, len, "%s:%s", username, password); + + *userpass_out = userpass; + return NSERROR_OK; +} + + +/** + * Handle a response from the UI when prompted for credentials + */ +static nserror +browser_window__handle_userpass_response(nsurl *url, + const char *realm, + const char *username, + const char *password, + void *pw) +{ + struct browser_window *bw = (struct browser_window *)pw; + char *userpass; + nserror err; + + err = browser_window__build_userpass(username, password, &userpass); + if (err != NSERROR_OK) { + return err; + } + + urldb_set_auth_details(url, realm, userpass); + + free(userpass); + + /** + * \todo QUERY - Eventually this should fill out the form *NOT* nav + * to the original location + */ + /* Finally navigate to the original loading parameters */ + if (bw->loading_content != NULL) { + /* We had a loading content (maybe auth page?) */ + browser_window_stop(bw); + browser_window_remove_caret(bw, false); + browser_window_destroy_children(bw); + browser_window_destroy_iframes(bw); + } + bw->internal_nav = false; + return browser_window__navigate_internal(bw, &bw->loading_parameters); +} + + +/** + * Handle login request (BAD_AUTH) during fetch + * + */ +static nserror +browser_window__handle_login(struct browser_window *bw, + const char *realm, + nsurl *url) { + char *username = NULL, *password = NULL; + nserror err = NSERROR_OK; + struct browser_fetch_parameters params; + + memset(¶ms, 0, sizeof(params)); + + /* Step one, retrieve what we have */ + err = browser_window__unpack_userpass( + urldb_get_auth_details(url, realm), + &username, &password); + if (err != NSERROR_OK) { + goto out; + } + + /* Step two, construct our fetch parameters */ + params.url = nsurl_ref(corestring_nsurl_about_query_auth); + params.referrer = nsurl_ref(url); + params.flags = BW_NAVIGATE_HISTORY | BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE | BW_NAVIGATE_INTERNAL; + + err = fetch_multipart_data_new_kv(¶ms.post_multipart, + "siteurl", + nsurl_access(url)); + if (err != NSERROR_OK) { + goto out; + } + + err = fetch_multipart_data_new_kv(¶ms.post_multipart, + "realm", + realm); + if (err != NSERROR_OK) { + goto out; + } + + err = fetch_multipart_data_new_kv(¶ms.post_multipart, + "username", + username); + if (err != NSERROR_OK) { + goto out; + } + + err = fetch_multipart_data_new_kv(¶ms.post_multipart, + "password", + password); + if (err != NSERROR_OK) { + goto out; + } + + /* Now we issue the fetch */ + bw->internal_nav = true; + err = browser_window__navigate_internal(bw, ¶ms); + + if (err != NSERROR_OK) { + goto out; + } + + err = guit->misc->login(url, realm, username, password, + browser_window__handle_userpass_response, bw); + + if (err == NSERROR_NOT_IMPLEMENTED) { + err = NSERROR_OK; + } + out: + if (username != NULL) { + free(username); + } + if (password != NULL) { + free(password); + } + browser_window__free_fetch_parameters(¶ms); + return err; +} + + +/** + * Handle a certificate verification request (BAD_CERTS) during a fetch + */ +static nserror +browser_window__handle_bad_certs(struct browser_window *bw, + nsurl *url) +{ + struct browser_fetch_parameters params; + nserror err; + /* Initially we don't know WHY the SSL cert was bad */ + const char *reason = messages_get_sslcode(SSL_CERT_ERR_UNKNOWN); + size_t depth; + nsurl *chainurl = NULL; + + memset(¶ms, 0, sizeof(params)); + + params.url = nsurl_ref(corestring_nsurl_about_query_ssl); + params.referrer = nsurl_ref(url); + params.flags = BW_NAVIGATE_HISTORY | BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE | BW_NAVIGATE_INTERNAL; + + err = fetch_multipart_data_new_kv(¶ms.post_multipart, + "siteurl", + nsurl_access(url)); + if (err != NSERROR_OK) { + goto out; + } + + if (bw->loading_cert_chain != NULL) { + for (depth = 0; depth < bw->loading_cert_chain->depth; ++depth) { + size_t idx = bw->loading_cert_chain->depth - (depth + 1); + ssl_cert_err err = bw->loading_cert_chain->certs[idx].err; + if (err != SSL_CERT_ERR_OK) { + reason = messages_get_sslcode(err); + break; + } + } + + err = cert_chain_to_query(bw->loading_cert_chain, &chainurl); + if (err != NSERROR_OK) { + goto out; + } + + err = fetch_multipart_data_new_kv(¶ms.post_multipart, + "chainurl", + nsurl_access(chainurl)); + if (err != NSERROR_OK) { + goto out; + } + } + + err = fetch_multipart_data_new_kv(¶ms.post_multipart, + "reason", + reason); + if (err != NSERROR_OK) { + goto out; + } + + /* Now we issue the fetch */ + bw->internal_nav = true; + err = browser_window__navigate_internal(bw, ¶ms); + if (err != NSERROR_OK) { + goto out; + } + + out: + browser_window__free_fetch_parameters(¶ms); + if (chainurl != NULL) + nsurl_unref(chainurl); + return err; +} + + +/** + * Handle a timeout during a fetch + */ +static nserror +browser_window__handle_timeout(struct browser_window *bw, nsurl *url) +{ + struct browser_fetch_parameters params; + nserror err; + + memset(¶ms, 0, sizeof(params)); + + params.url = nsurl_ref(corestring_nsurl_about_query_timeout); + params.referrer = nsurl_ref(url); + params.flags = BW_NAVIGATE_HISTORY | BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE | BW_NAVIGATE_INTERNAL; + + err = fetch_multipart_data_new_kv(¶ms.post_multipart, + "siteurl", + nsurl_access(url)); + if (err != NSERROR_OK) { + goto out; + } + + /* Now we issue the fetch */ + bw->internal_nav = true; + err = browser_window__navigate_internal(bw, ¶ms); + if (err != NSERROR_OK) { + goto out; + } + + out: + browser_window__free_fetch_parameters(¶ms); + return err; +} + + +/** + * Handle non specific errors during a fetch + */ +static nserror +browser_window__handle_fetcherror(struct browser_window *bw, + const char *reason, + nsurl *url) +{ + struct browser_fetch_parameters params; + nserror err; + + memset(¶ms, 0, sizeof(params)); + + params.url = nsurl_ref(corestring_nsurl_about_query_fetcherror); + params.referrer = nsurl_ref(url); + params.flags = BW_NAVIGATE_HISTORY | BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE | BW_NAVIGATE_INTERNAL; + + err = fetch_multipart_data_new_kv(¶ms.post_multipart, + "siteurl", + nsurl_access(url)); + if (err != NSERROR_OK) { + goto out; + } + + err = fetch_multipart_data_new_kv(¶ms.post_multipart, + "reason", + reason); + if (err != NSERROR_OK) { + goto out; + } + + /* Now we issue the fetch */ + bw->internal_nav = true; + err = browser_window__navigate_internal(bw, ¶ms); + if (err != NSERROR_OK) { + goto out; + } + + out: + browser_window__free_fetch_parameters(¶ms); + return err; +} + + +/** + * Handle errors during content fetch + */ +static nserror +browser_window__handle_error(struct browser_window *bw, + hlcache_handle *c, + const hlcache_event *event) +{ + const char *message = event->data.errordata.errormsg; + nserror code = event->data.errordata.errorcode; + nserror res; + nsurl *url = hlcache_handle_get_url(c); + + /* Unexpected OK? */ + assert(code != NSERROR_OK); + + if (message == NULL) { + message = messages_get_errorcode(code); + } else { + message = messages_get(message); + } + + if (c == bw->loading_content) { + bw->loading_content = NULL; + } else if (c == bw->current_content) { + bw->current_content = NULL; + browser_window_remove_caret(bw, false); + } + + hlcache_handle_release(c); + + switch (code) { + case NSERROR_BAD_AUTH: + res = browser_window__handle_login(bw, message, url); + break; + + case NSERROR_BAD_CERTS: + res = browser_window__handle_bad_certs(bw, url); + break; + + case NSERROR_TIMEOUT: + res = browser_window__handle_timeout(bw, url); + break; + + default: + res = browser_window__handle_fetcherror(bw, message, url); + break; + } + + return res; +} + + +/** + * Update URL bar for a given browser window to given URL + * + * \param bw Browser window to update URL bar for. + * \param url URL for content displayed by bw including any fragment. + */ +static inline nserror +browser_window_refresh_url_bar_internal(struct browser_window *bw, nsurl *url) +{ + assert(bw); + assert(url); + + if ((bw->parent != NULL) || (bw->window == NULL)) { + /* Not root window or no gui window so do not set a URL */ + return NSERROR_OK; + } + + return guit->window->set_url(bw->window, url); +} + + +/** + * Browser window content event callback handler. + */ +static nserror +browser_window_callback(hlcache_handle *c, const hlcache_event *event, void *pw) +{ + struct browser_window *bw = pw; + nserror res = NSERROR_OK; + + switch (event->type) { + case CONTENT_MSG_SSL_CERTS: + /* SSL certificate information has arrived, store it */ + cert_chain_free(bw->loading_cert_chain); + cert_chain_dup(event->data.chain, &bw->loading_cert_chain); + break; + + case CONTENT_MSG_LOG: + browser_window_console_log(bw, + event->data.log.src, + event->data.log.msg, + event->data.log.msglen, + event->data.log.flags); + break; + + case CONTENT_MSG_DOWNLOAD: + assert(bw->loading_content == c); + + browser_window_convert_to_download(bw, event->data.download); + + if (bw->current_content != NULL) { + browser_window_refresh_url_bar(bw); + } + break; + + case CONTENT_MSG_LOADING: + assert(bw->loading_content == c); + +#ifdef WITH_THEME_INSTALL + if (content_get_type(c) == CONTENT_THEME) { + theme_install_start(c); + bw->loading_content = NULL; + browser_window_stop_throbber(bw); + } else +#endif + { + bw->refresh_interval = -1; + browser_window_set_status(bw, + content_get_status_message(c)); + } + break; + + case CONTENT_MSG_READY: + assert(bw->loading_content == c); + + res = browser_window_content_ready(bw); + break; + + case CONTENT_MSG_DONE: + assert(bw->current_content == c); + + res = browser_window_content_done(bw); + break; + + case CONTENT_MSG_ERROR: + res = browser_window__handle_error(bw, c, event); + break; + + case CONTENT_MSG_REDIRECT: + if (urldb_add_url(event->data.redirect.from)) { + urldb_update_url_visit_data(event->data.redirect.from); + } + browser_window_refresh_url_bar_internal(bw, event->data.redirect.to); + break; + + case CONTENT_MSG_STATUS: + if (event->data.explicit_status_text == NULL) { + /* Object content's status text updated */ + const char *status = NULL; + if (bw->loading_content != NULL) { + /* Give preference to any loading content */ + status = content_get_status_message( + bw->loading_content); + } + + if (status == NULL) { + status = content_get_status_message(c); + } + + if (status != NULL) { + browser_window_set_status(bw, status); + } + } else { + /* Object content wants to set explicit message */ + browser_window_set_status(bw, + event->data.explicit_status_text); + } + break; + + case CONTENT_MSG_REFORMAT: + if (c == bw->current_content) { + /* recompute frameset */ + browser_window_recalculate_frameset(bw); + + /* recompute iframe positions, sizes and scrollbars */ + browser_window_recalculate_iframes(bw); + } + + /* Hide any caret, but don't remove it */ + browser_window_remove_caret(bw, true); + + if (!(event->data.background)) { + /* Reformatted content should be redrawn */ + browser_window_update(bw, false); + } + break; + + case CONTENT_MSG_REDRAW: + { + struct rect rect = { + .x0 = event->data.redraw.x, + .y0 = event->data.redraw.y, + .x1 = event->data.redraw.x + event->data.redraw.width, + .y1 = event->data.redraw.y + event->data.redraw.height + }; + + browser_window_invalidate_rect(bw, &rect); + } + break; + + case CONTENT_MSG_REFRESH: + bw->refresh_interval = event->data.delay * 100; + break; + + case CONTENT_MSG_LINK: /* content has an rfc5988 link element */ + { + bool match; + + /* Handle "icon" and "shortcut icon" */ + if ((lwc_string_caseless_isequal( + event->data.rfc5988_link->rel, + corestring_lwc_icon, + &match) == lwc_error_ok && match) || + (lwc_string_caseless_isequal( + event->data.rfc5988_link->rel, + corestring_lwc_shortcut_icon, + &match) == lwc_error_ok && match)) { + /* it's a favicon perhaps start a fetch for it */ + browser_window_update_favicon(c, bw, + event->data.rfc5988_link); + } + } + break; + + case CONTENT_MSG_GETTHREAD: + { + /* only the content object created by the browser + * window requires a new javascript thread object + */ + jsthread *thread; + assert(bw->loading_content == c); + + if (js_newthread(bw->jsheap, + bw, + hlcache_handle_get_content(c), + &thread) == NSERROR_OK) { + /* The content which is requesting the thread + * is required to keep hold of it and + * to destroy it when it is finished with it. + */ + *(event->data.jsthread) = thread; + } + } + break; + + case CONTENT_MSG_GETDIMS: + { + int width; + int height; + + browser_window_get_dimensions(bw, &width, &height); + + *(event->data.getdims.viewport_width) = width / bw->scale; + *(event->data.getdims.viewport_height) = height / bw->scale; + break; + } + + case CONTENT_MSG_SCROLL: + { + struct rect rect = { + .x0 = event->data.scroll.x0, + .y0 = event->data.scroll.y0, + }; + + /* Content wants to be scrolled */ + if (bw->current_content != c) { + break; + } + + if (event->data.scroll.area) { + rect.x1 = event->data.scroll.x1; + rect.y1 = event->data.scroll.y1; + } else { + rect.x1 = event->data.scroll.x0; + rect.y1 = event->data.scroll.y0; + } + browser_window_set_scroll(bw, &rect); + + break; + } + + case CONTENT_MSG_DRAGSAVE: + { + /* Content wants drag save of a content */ + struct browser_window *root = browser_window_get_root(bw); + hlcache_handle *save = event->data.dragsave.content; + + if (save == NULL) { + save = c; + } + + switch(event->data.dragsave.type) { + case CONTENT_SAVE_ORIG: + guit->window->drag_save_object(root->window, + save, + GUI_SAVE_OBJECT_ORIG); + break; + + case CONTENT_SAVE_NATIVE: + guit->window->drag_save_object(root->window, + save, + GUI_SAVE_OBJECT_NATIVE); + break; + + case CONTENT_SAVE_COMPLETE: + guit->window->drag_save_object(root->window, + save, + GUI_SAVE_COMPLETE); + break; + + case CONTENT_SAVE_SOURCE: + guit->window->drag_save_object(root->window, + save, + GUI_SAVE_SOURCE); + break; + } + } + break; + + case CONTENT_MSG_SAVELINK: + { + /* Content wants a link to be saved */ + struct browser_window *root = browser_window_get_root(bw); + guit->window->save_link(root->window, + event->data.savelink.url, + event->data.savelink.title); + } + break; + + case CONTENT_MSG_POINTER: + /* Content wants to have specific mouse pointer */ + browser_window_set_pointer(bw, event->data.pointer); + break; + + case CONTENT_MSG_DRAG: + { + browser_drag_type bdt = DRAGGING_NONE; + + switch (event->data.drag.type) { + case CONTENT_DRAG_NONE: + bdt = DRAGGING_NONE; + break; + case CONTENT_DRAG_SCROLL: + bdt = DRAGGING_CONTENT_SCROLLBAR; + break; + case CONTENT_DRAG_SELECTION: + bdt = DRAGGING_SELECTION; + break; + } + browser_window_set_drag_type(bw, bdt, event->data.drag.rect); + } + break; + + case CONTENT_MSG_CARET: + switch (event->data.caret.type) { + case CONTENT_CARET_REMOVE: + browser_window_remove_caret(bw, false); + break; + case CONTENT_CARET_HIDE: + browser_window_remove_caret(bw, true); + break; + case CONTENT_CARET_SET_POS: + browser_window_place_caret(bw, + event->data.caret.pos.x, + event->data.caret.pos.y, + event->data.caret.pos.height, + event->data.caret.pos.clip); + break; + } + break; + + case CONTENT_MSG_SELECTION: + browser_window_set_selection(bw, + event->data.selection.selection, + event->data.selection.read_only); + break; + + case CONTENT_MSG_SELECTMENU: + if (event->data.select_menu.gadget->type == GADGET_SELECT) { + struct browser_window *root = + browser_window_get_root(bw); + guit->window->create_form_select_menu(root->window, + event->data.select_menu.gadget); + } + + break; + + case CONTENT_MSG_GADGETCLICK: + if (event->data.gadget_click.gadget->type == GADGET_FILE) { + struct browser_window *root = + browser_window_get_root(bw); + guit->window->file_gadget_open(root->window, c, + event->data.gadget_click.gadget); + } + + break; + + + case CONTENT_MSG_TEXTSEARCH: + switch (event->data.textsearch.type) { + case CONTENT_TEXTSEARCH_FIND: + guit->search->hourglass(event->data.textsearch.state, + event->data.textsearch.ctx); + break; + + case CONTENT_TEXTSEARCH_MATCH: + guit->search->status(event->data.textsearch.state, + event->data.textsearch.ctx); + break; + + case CONTENT_TEXTSEARCH_BACK: + guit->search->back_state(event->data.textsearch.state, + event->data.textsearch.ctx); + break; + + case CONTENT_TEXTSEARCH_FORWARD: + guit->search->forward_state(event->data.textsearch.state, + event->data.textsearch.ctx); + break; + + case CONTENT_TEXTSEARCH_RECENT: + guit->search->add_recent(event->data.textsearch.string, + event->data.textsearch.ctx); + + break; + } + break; + + default: + break; + } + + return res; +} + + +/** + * internal scheduled reformat callback. + * + * scheduled reformat callback to allow reformats from unthreaded context. + * + * \param vbw The browser window to be reformatted + */ +static void scheduled_reformat(void *vbw) +{ + struct browser_window *bw = vbw; + int width; + int height; + nserror res; + + res = guit->window->get_dimensions(bw->window, &width, &height); + if (res == NSERROR_OK) { + browser_window_reformat(bw, false, width, height); + } +} + +/* exported interface documented in desktop/browser_private.h */ +nserror browser_window_destroy_internal(struct browser_window *bw) +{ + assert(bw); + + browser_window_destroy_children(bw); + browser_window_destroy_iframes(bw); + + /* Destroy scrollbars */ + if (bw->scroll_x != NULL) { + scrollbar_destroy(bw->scroll_x); + } + + if (bw->scroll_y != NULL) { + scrollbar_destroy(bw->scroll_y); + } + + /* clear any pending callbacks */ + guit->misc->schedule(-1, browser_window_refresh, bw); + NSLOG(netsurf, INFO, + "Clearing reformat schedule for browser window %p", bw); + guit->misc->schedule(-1, scheduled_reformat, bw); + + /* If this brower window is not the root window, and has focus, unset + * the root browser window's focus pointer. */ + if (!bw->window) { + struct browser_window *top = browser_window_get_root(bw); + + if (top->focus == bw) + top->focus = top; + + if (top->selection.bw == bw) { + browser_window_set_selection(top, false, false); + } + } + + /* Destruction order is important: we must ensure that the frontend + * destroys any window(s) associated with this browser window before + * we attempt any destructive cleanup. + */ + + if (bw->window) { + /* Only the root window has a GUI window */ + guit->window->destroy(bw->window); + } + + if (bw->loading_content != NULL) { + hlcache_handle_abort(bw->loading_content); + hlcache_handle_release(bw->loading_content); + bw->loading_content = NULL; + } + + if (bw->current_content != NULL) { + content_close(bw->current_content); + hlcache_handle_release(bw->current_content); + bw->current_content = NULL; + } + + if (bw->favicon.loading != NULL) { + hlcache_handle_abort(bw->favicon.loading); + hlcache_handle_release(bw->favicon.loading); + bw->favicon.loading = NULL; + } + + if (bw->favicon.current != NULL) { + content_close(bw->favicon.current); + hlcache_handle_release(bw->favicon.current); + bw->favicon.current = NULL; + } + + if (bw->jsheap != NULL) { + js_destroyheap(bw->jsheap); + bw->jsheap = NULL; + } + + /* These simply free memory, so are safe here */ + + if (bw->frag_id != NULL) { + lwc_string_unref(bw->frag_id); + } + + browser_window_history_destroy(bw); + + cert_chain_free(bw->current_cert_chain); + cert_chain_free(bw->loading_cert_chain); + bw->current_cert_chain = NULL; + bw->loading_cert_chain = NULL; + + free(bw->name); + free(bw->status.text); + bw->status.text = NULL; + browser_window__free_fetch_parameters(&bw->current_parameters); + browser_window__free_fetch_parameters(&bw->loading_parameters); + NSLOG(netsurf, INFO, "Status text cache match:miss %d:%d", + bw->status.match, bw->status.miss); + + return NSERROR_OK; +} + + +/** + * Set browser window scale. + * + * \param bw Browser window. + * \param absolute scale value. + * \return NSERROR_OK on success else error code + */ +static nserror +browser_window_set_scale_internal(struct browser_window *bw, float scale) +{ + int i; + nserror res = NSERROR_OK; + + /* do not apply tiny changes in scale */ + if (fabs(bw->scale - scale) < 0.0001) + return res; + + bw->scale = scale; + + if (bw->current_content != NULL) { + if (content_can_reformat(bw->current_content) == false) { + browser_window_update(bw, false); + } else { + res = browser_window_schedule_reformat(bw); + } + } + + /* scale frames */ + for (i = 0; i < (bw->cols * bw->rows); i++) { + res = browser_window_set_scale_internal(&bw->children[i], scale); + } + + /* scale iframes */ + for (i = 0; i < bw->iframe_count; i++) { + res = browser_window_set_scale_internal(&bw->iframes[i], scale); + } + + return res; +} + + +/** + * Find browser window. + * + * \param bw Browser window. + * \param target Name of target. + * \param depth Depth to scan. + * \param page The browser window page. + * \param rdepth The rdepth. + * \param bw_target the output browser window. + */ +static void +browser_window_find_target_internal(struct browser_window *bw, + const char *target, + int depth, + struct browser_window *page, + int *rdepth, + struct browser_window **bw_target) +{ + int i; + + if ((bw->name) && (!strcasecmp(bw->name, target))) { + if ((bw == page) || (depth > *rdepth)) { + *rdepth = depth; + *bw_target = bw; + } + } + + if ((!bw->children) && (!bw->iframes)) + return; + + depth++; + + if (bw->children != NULL) { + for (i = 0; i < (bw->cols * bw->rows); i++) { + if ((bw->children[i].name) && + (!strcasecmp(bw->children[i].name, + target))) { + if ((page == &bw->children[i]) || + (depth > *rdepth)) { + *rdepth = depth; + *bw_target = &bw->children[i]; + } + } + if (bw->children[i].children) + browser_window_find_target_internal( + &bw->children[i], + target, depth, page, + rdepth, bw_target); + } + } + + if (bw->iframes != NULL) { + for (i = 0; i < bw->iframe_count; i++) { + browser_window_find_target_internal(&bw->iframes[i], + target, + depth, + page, + rdepth, + bw_target); + } + } +} + + +/** + * Handles the end of a drag operation in a browser window. + * + * \param bw browser window + * \param mouse state of mouse buttons and modifier keys + * \param x coordinate of mouse + * \param y coordinate of mouse + * + * \todo Remove this function, once these things are associated with content, + * rather than bw. + */ +static void +browser_window_mouse_drag_end(struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + int scr_x, scr_y; + + switch (bw->drag.type) { + case DRAGGING_SELECTION: + case DRAGGING_OTHER: + case DRAGGING_CONTENT_SCROLLBAR: + /* Drag handled by content handler */ + break; + + case DRAGGING_SCR_X: + + browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y); + + scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); + scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); + + scrollbar_mouse_drag_end(bw->scroll_x, mouse, scr_x, scr_y); + + bw->drag.type = DRAGGING_NONE; + break; + + case DRAGGING_SCR_Y: + + browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y); + + scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); + scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); + + scrollbar_mouse_drag_end(bw->scroll_y, mouse, scr_x, scr_y); + + bw->drag.type = DRAGGING_NONE; + break; + + default: + browser_window_set_drag_type(bw, DRAGGING_NONE, NULL); + break; + } +} + +/** + * Process mouse click event + * + * \param bw The browsing context receiving the event + * \param mouse The mouse event state + * \param x The scaled x co-ordinate of the event + * \param y The scaled y co-ordinate of the event + */ +static void +browser_window_mouse_click_internal(struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + hlcache_handle *c = bw->current_content; + const char *status = NULL; + browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; + + if (bw->children) { + /* Browser window has children (frames) */ + struct browser_window *child; + int cur_child; + int children = bw->rows * bw->cols; + + for (cur_child = 0; cur_child < children; cur_child++) { + + child = &bw->children[cur_child]; + + if ((x < child->x) || + (y < child->y) || + (child->x + child->width < x) || + (child->y + child->height < y)) { + /* Click not in this child */ + continue; + } + + /* It's this child that contains the click; pass it + * on to child. */ + browser_window_mouse_click_internal( + child, + mouse, + x - child->x + scrollbar_get_offset(child->scroll_x), + y - child->y + scrollbar_get_offset(child->scroll_y)); + + /* Mouse action was for this child, we're done */ + return; + } + + return; + } + + if (!c) + return; + + if (bw->scroll_x != NULL) { + int scr_x, scr_y; + browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y); + scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); + scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); + + if (scr_x > 0 && scr_x < get_horz_scrollbar_len(bw) && + scr_y > 0 && scr_y < SCROLLBAR_WIDTH) { + status = scrollbar_mouse_status_to_message( + scrollbar_mouse_action( + bw->scroll_x, mouse, + scr_x, scr_y)); + pointer = BROWSER_POINTER_DEFAULT; + + if (status != NULL) + browser_window_set_status(bw, status); + + browser_window_set_pointer(bw, pointer); + return; + } + } + + if (bw->scroll_y != NULL) { + int scr_x, scr_y; + browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y); + scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); + scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); + + if (scr_y > 0 && scr_y < get_vert_scrollbar_len(bw) && + scr_x > 0 && scr_x < SCROLLBAR_WIDTH) { + status = scrollbar_mouse_status_to_message( + scrollbar_mouse_action( + bw->scroll_y, + mouse, + scr_x, + scr_y)); + pointer = BROWSER_POINTER_DEFAULT; + + if (status != NULL) { + browser_window_set_status(bw, status); + } + + browser_window_set_pointer(bw, pointer); + return; + } + } + + switch (content_get_type(c)) { + case CONTENT_HTML: + case CONTENT_TEXTPLAIN: + { + /* Give bw focus */ + struct browser_window *root_bw = browser_window_get_root(bw); + if (bw != root_bw->focus) { + browser_window_remove_caret(bw, false); + browser_window_set_selection(bw, false, true); + root_bw->focus = bw; + } + + /* Pass mouse action to content */ + content_mouse_action(c, bw, mouse, x, y); + } + break; + default: + if (mouse & BROWSER_MOUSE_MOD_2) { + if (mouse & BROWSER_MOUSE_DRAG_2) { + guit->window->drag_save_object(bw->window, c, + GUI_SAVE_OBJECT_NATIVE); + } else if (mouse & BROWSER_MOUSE_DRAG_1) { + guit->window->drag_save_object(bw->window, c, + GUI_SAVE_OBJECT_ORIG); + } + } else if (mouse & (BROWSER_MOUSE_DRAG_1 | + BROWSER_MOUSE_DRAG_2)) { + browser_window_page_drag_start(bw, x, y); + browser_window_set_pointer(bw, BROWSER_POINTER_MOVE); + } + break; + } +} + + +/** + * Process mouse movement event + * + * \param bw The browsing context receiving the event + * \param mouse The mouse event state + * \param x The scaled x co-ordinate of the event + * \param y The scaled y co-ordinate of the event + */ +static void +browser_window_mouse_track_internal(struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + hlcache_handle *c = bw->current_content; + const char *status = NULL; + browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; + + if (bw->window != NULL && bw->drag.window && bw != bw->drag.window) { + /* This is the root browser window and there's an active drag + * in a sub window. + * Pass the mouse action straight on to that bw. */ + struct browser_window *drag_bw = bw->drag.window; + int off_x = 0; + int off_y = 0; + + browser_window_get_position(drag_bw, true, &off_x, &off_y); + + if (drag_bw->browser_window_type == BROWSER_WINDOW_FRAME) { + browser_window_mouse_track_internal(drag_bw, + mouse, + x - off_x, + y - off_y); + + } else if (drag_bw->browser_window_type == BROWSER_WINDOW_IFRAME) { + browser_window_mouse_track_internal(drag_bw, mouse, + x - off_x / bw->scale, + y - off_y / bw->scale); + } + return; + } + + if (bw->children) { + /* Browser window has children (frames) */ + struct browser_window *child; + int cur_child; + int children = bw->rows * bw->cols; + + for (cur_child = 0; cur_child < children; cur_child++) { + + child = &bw->children[cur_child]; + + if ((x < child->x) || + (y < child->y) || + (child->x + child->width < x) || + (child->y + child->height < y)) { + /* Click not in this child */ + continue; + } + + /* It's this child that contains the mouse; pass + * mouse action on to child */ + browser_window_mouse_track_internal( + child, + mouse, + x - child->x + scrollbar_get_offset(child->scroll_x), + y - child->y + scrollbar_get_offset(child->scroll_y)); + + /* Mouse action was for this child, we're done */ + return; + } + + /* Odd if we reached here, but nothing else can use the click + * when there are children. */ + return; + } + + if (c == NULL && bw->drag.type != DRAGGING_FRAME) { + return; + } + + if (bw->drag.type != DRAGGING_NONE && !mouse) { + browser_window_mouse_drag_end(bw, mouse, x, y); + } + + /* Browser window's horizontal scrollbar */ + if (bw->scroll_x != NULL && bw->drag.type != DRAGGING_SCR_Y) { + int scr_x, scr_y; + browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y); + scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); + scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); + + if ((bw->drag.type == DRAGGING_SCR_X) || + (scr_x > 0 && + scr_x < get_horz_scrollbar_len(bw) && + scr_y > 0 && + scr_y < SCROLLBAR_WIDTH && + bw->drag.type == DRAGGING_NONE)) { + /* Start a scrollbar drag, or continue existing drag */ + status = scrollbar_mouse_status_to_message( + scrollbar_mouse_action(bw->scroll_x, + mouse, + scr_x, + scr_y)); + pointer = BROWSER_POINTER_DEFAULT; + + if (status != NULL) { + browser_window_set_status(bw, status); + } + + browser_window_set_pointer(bw, pointer); + return; + } + } + + /* Browser window's vertical scrollbar */ + if (bw->scroll_y != NULL) { + int scr_x, scr_y; + browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y); + scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); + scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); + + if ((bw->drag.type == DRAGGING_SCR_Y) || + (scr_y > 0 && + scr_y < get_vert_scrollbar_len(bw) && + scr_x > 0 && + scr_x < SCROLLBAR_WIDTH && + bw->drag.type == DRAGGING_NONE)) { + /* Start a scrollbar drag, or continue existing drag */ + status = scrollbar_mouse_status_to_message( + scrollbar_mouse_action(bw->scroll_y, + mouse, + scr_x, + scr_y)); + pointer = BROWSER_POINTER_DEFAULT; + + if (status != NULL) { + browser_window_set_status(bw, status); + } + + browser_window_set_pointer(bw, pointer); + return; + } + } + + if (bw->drag.type == DRAGGING_FRAME) { + browser_window_resize_frame(bw, bw->x + x, bw->y + y); + } else if (bw->drag.type == DRAGGING_PAGE_SCROLL) { + /* mouse movement since drag started */ + struct rect rect; + + rect.x0 = bw->drag.start_x - x; + rect.y0 = bw->drag.start_y - y; + + /* new scroll offsets */ + rect.x0 += bw->drag.start_scroll_x; + rect.y0 += bw->drag.start_scroll_y; + + bw->drag.start_scroll_x = rect.x1 = rect.x0; + bw->drag.start_scroll_y = rect.y1 = rect.y0; + + browser_window_set_scroll(bw, &rect); + } else { + assert(c != NULL); + content_mouse_track(c, bw, mouse, x, y); + } +} + + +/** + * perform a scroll operation at a given coordinate + * + * \param bw The browsing context receiving the event + * \param x The scaled x co-ordinate of the event + * \param y The scaled y co-ordinate of the event + */ +static bool +browser_window_scroll_at_point_internal(struct browser_window *bw, + int x, int y, + int scrx, int scry) +{ + assert(bw != NULL); + + /* Handle (i)frame scroll offset (core-managed browser windows only) */ + x += scrollbar_get_offset(bw->scroll_x); + y += scrollbar_get_offset(bw->scroll_y); + + if (bw->children) { + /* Browser window has children, so pass request on to + * appropriate child */ + struct browser_window *bwc; + int cur_child; + int children = bw->rows * bw->cols; + + /* Loop through all children of bw */ + for (cur_child = 0; cur_child < children; cur_child++) { + /* Set current child */ + bwc = &bw->children[cur_child]; + + /* Skip this frame if (x, y) coord lies outside */ + if (x < bwc->x || bwc->x + bwc->width < x || + y < bwc->y || bwc->y + bwc->height < y) + continue; + + /* Pass request into this child */ + return browser_window_scroll_at_point_internal( + bwc, + (x - bwc->x), + (y - bwc->y), + scrx, scry); + } + } + + /* Try to scroll any current content */ + if (bw->current_content != NULL && + content_scroll_at_point(bw->current_content, x, y, scrx, scry) == true) { + /* Scroll handled by current content */ + return true; + } + + /* Try to scroll this window. */ + return (int)scrollbar_scroll(bw->scroll_y, scry) | + (int)scrollbar_scroll(bw->scroll_x, scrx); +} + + +/** + * allows a dragged file to be dropped into a browser window at a position + * + * \param bw The browsing context receiving the event + * \param x The scaled x co-ordinate of the event + * \param y The scaled y co-ordinate of the event + * \param file filename to be put in the widget + */ +static bool +browser_window_drop_file_at_point_internal(struct browser_window *bw, + int x, int y, + char *file) +{ + assert(bw != NULL); + + /* Handle (i)frame scroll offset (core-managed browser windows only) */ + x += scrollbar_get_offset(bw->scroll_x); + y += scrollbar_get_offset(bw->scroll_y); + + if (bw->children) { + /* Browser window has children, so pass request on to + * appropriate child */ + struct browser_window *bwc; + int cur_child; + int children = bw->rows * bw->cols; + + /* Loop through all children of bw */ + for (cur_child = 0; cur_child < children; cur_child++) { + /* Set current child */ + bwc = &bw->children[cur_child]; + + /* Skip this frame if (x, y) coord lies outside */ + if (x < bwc->x || bwc->x + bwc->width < x || + y < bwc->y || bwc->y + bwc->height < y) + continue; + + /* Pass request into this child */ + return browser_window_drop_file_at_point_internal( + bwc, + (x - bwc->x), + (y - bwc->y), + file); + } + } + + /* Pass file drop on to any content */ + if (bw->current_content != NULL) { + return content_drop_file_at_point(bw->current_content, + x, y, file); + } + + return false; +} + + +/** + * Check if this is an internal navigation URL. + * + * This safely checks if the given url is an internal navigation even + * for urls with no scheme or path. + * + * \param url The URL to check + * \return true if an internal navigation url else false + */ +static bool +is_internal_navigate_url(nsurl *url) +{ + bool is_internal = false; + lwc_string *scheme, *path; + + scheme = nsurl_get_component(url, NSURL_SCHEME); + if (scheme != NULL) { + path = nsurl_get_component(url, NSURL_PATH); + if (path != NULL) { + if (scheme == corestring_lwc_about) { + if (path == corestring_lwc_query_auth) { + is_internal = true; + } else if (path == corestring_lwc_query_ssl) { + is_internal = true; + } else if (path == corestring_lwc_query_timeout) { + is_internal = true; + } else if (path == corestring_lwc_query_fetcherror) { + is_internal = true; + } + } + lwc_string_unref(path); + } + lwc_string_unref(scheme); + } + return is_internal; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +nserror +browser_window_get_name(struct browser_window *bw, const char **out_name) +{ + assert(bw != NULL); + + *out_name = bw->name; + + return NSERROR_OK; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +nserror +browser_window_set_name(struct browser_window *bw, const char *name) +{ + char *nname = NULL; + + assert(bw != NULL); + + if (name != NULL) { + nname = strdup(name); + if (nname == NULL) { + return NSERROR_NOMEM; + } + } + + if (bw->name != NULL) { + free(bw->name); + } + + bw->name = nname; + + return NSERROR_OK; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +bool +browser_window_redraw(struct browser_window *bw, + int x, int y, + const struct rect *clip, + const struct redraw_context *ctx) +{ + struct redraw_context new_ctx = *ctx; + int width = 0; + int height = 0; + bool plot_ok = true; + content_type content_type; + struct content_redraw_data data; + struct rect content_clip; + nserror res; + + if (bw == NULL) { + NSLOG(netsurf, INFO, "NULL browser window"); + return false; + } + + x /= bw->scale; + y /= bw->scale; + + if ((bw->current_content == NULL) && + (bw->children == NULL)) { + /* Browser window has no content, render blank fill */ + ctx->plot->clip(ctx, clip); + return (ctx->plot->rectangle(ctx, plot_style_fill_white, clip) == NSERROR_OK); + } + + /* Browser window has content OR children (frames) */ + if ((bw->window != NULL) && + (ctx->plot->option_knockout)) { + /* Root browser window: start knockout */ + knockout_plot_start(ctx, &new_ctx); + } + + new_ctx.plot->clip(ctx, clip); + + /* Handle redraw of any browser window children */ + if (bw->children) { + struct browser_window *child; + int cur_child; + int children = bw->rows * bw->cols; + + if (bw->window != NULL) { + /* Root browser window; start with blank fill */ + plot_ok &= (new_ctx.plot->rectangle(ctx, + plot_style_fill_white, + clip) == NSERROR_OK); + } + + /* Loop through all children of bw */ + for (cur_child = 0; cur_child < children; cur_child++) { + /* Set current child */ + child = &bw->children[cur_child]; + + /* Get frame edge area in global coordinates */ + content_clip.x0 = (x + child->x) * child->scale; + content_clip.y0 = (y + child->y) * child->scale; + content_clip.x1 = content_clip.x0 + + child->width * child->scale; + content_clip.y1 = content_clip.y0 + + child->height * child->scale; + + /* Intersect it with clip rectangle */ + if (content_clip.x0 < clip->x0) + content_clip.x0 = clip->x0; + if (content_clip.y0 < clip->y0) + content_clip.y0 = clip->y0; + if (clip->x1 < content_clip.x1) + content_clip.x1 = clip->x1; + if (clip->y1 < content_clip.y1) + content_clip.y1 = clip->y1; + + /* Skip this frame if it lies outside clip rectangle */ + if (content_clip.x0 >= content_clip.x1 || + content_clip.y0 >= content_clip.y1) + continue; + + /* Redraw frame */ + plot_ok &= browser_window_redraw(child, + x + child->x, + y + child->y, + &content_clip, + &new_ctx); + } + + /* Nothing else to redraw for browser windows with children; + * cleanup and return + */ + if (bw->window != NULL && ctx->plot->option_knockout) { + /* Root browser window: knockout end */ + knockout_plot_end(ctx); + } + + return plot_ok; + } + + /* Handle browser windows with content to redraw */ + + content_type = content_get_type(bw->current_content); + if (content_type != CONTENT_HTML && content_type != CONTENT_TEXTPLAIN) { + /* Set render area according to scale */ + width = content_get_width(bw->current_content) * bw->scale; + height = content_get_height(bw->current_content) * bw->scale; + + /* Non-HTML may not fill viewport to extents, so plot white + * background fill */ + plot_ok &= (new_ctx.plot->rectangle(&new_ctx, + plot_style_fill_white, + clip) == NSERROR_OK); + } + + /* Set up content redraw data */ + data.x = x - scrollbar_get_offset(bw->scroll_x); + data.y = y - scrollbar_get_offset(bw->scroll_y); + data.width = width; + data.height = height; + + data.background_colour = 0xFFFFFF; + data.scale = bw->scale; + data.repeat_x = false; + data.repeat_y = false; + + content_clip = *clip; + + if (!bw->window) { + int x0 = x * bw->scale; + int y0 = y * bw->scale; + int x1 = (x + bw->width - ((bw->scroll_y != NULL) ? + SCROLLBAR_WIDTH : 0)) * bw->scale; + int y1 = (y + bw->height - ((bw->scroll_x != NULL) ? + SCROLLBAR_WIDTH : 0)) * bw->scale; + + if (content_clip.x0 < x0) content_clip.x0 = x0; + if (content_clip.y0 < y0) content_clip.y0 = y0; + if (x1 < content_clip.x1) content_clip.x1 = x1; + if (y1 < content_clip.y1) content_clip.y1 = y1; + } + + /* Render the content */ + plot_ok &= content_redraw(bw->current_content, &data, + &content_clip, &new_ctx); + + /* Back to full clip rect */ + new_ctx.plot->clip(&new_ctx, clip); + + if (!bw->window) { + /* Render scrollbars */ + int off_x, off_y; + if (bw->scroll_x != NULL) { + browser_window_get_scrollbar_pos(bw, true, + &off_x, &off_y); + res = scrollbar_redraw(bw->scroll_x, + x + off_x, y + off_y, clip, + bw->scale, &new_ctx); + if (res != NSERROR_OK) { + plot_ok = false; + } + } + if (bw->scroll_y != NULL) { + browser_window_get_scrollbar_pos(bw, false, + &off_x, &off_y); + res = scrollbar_redraw(bw->scroll_y, + x + off_x, y + off_y, clip, + bw->scale, &new_ctx); + if (res != NSERROR_OK) { + plot_ok = false; + } + } + } + + if (bw->window != NULL && ctx->plot->option_knockout) { + /* Root browser window: end knockout */ + knockout_plot_end(ctx); + } + + return plot_ok; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +bool browser_window_redraw_ready(struct browser_window *bw) +{ + if (bw == NULL) { + NSLOG(netsurf, INFO, "NULL browser window"); + return false; + } else if (bw->current_content != NULL) { + /* Can't render locked contents */ + return !content_is_locked(bw->current_content); + } + + return true; +} + + +/* exported interface, documented in browser_private.h */ +void browser_window_update_extent(struct browser_window *bw) +{ + if (bw->window != NULL) { + /* Front end window */ + guit->window->event(bw->window, GW_EVENT_UPDATE_EXTENT); + } else { + /* Core-managed browser window */ + browser_window_handle_scrollbars(bw); + } +} + + +/* exported interface, documented in netsurf/browser_window.h */ +void +browser_window_get_position(struct browser_window *bw, + bool root, + int *pos_x, + int *pos_y) +{ + *pos_x = 0; + *pos_y = 0; + + assert(bw != NULL); + + while (bw) { + switch (bw->browser_window_type) { + + case BROWSER_WINDOW_FRAMESET: + *pos_x += bw->x * bw->scale; + *pos_y += bw->y * bw->scale; + break; + + case BROWSER_WINDOW_NORMAL: + /* There is no offset to the root browser window */ + break; + + case BROWSER_WINDOW_FRAME: + /* Iframe and Frame handling is identical; + * fall though */ + case BROWSER_WINDOW_IFRAME: + *pos_x += (bw->x - scrollbar_get_offset(bw->scroll_x)) * + bw->scale; + *pos_y += (bw->y - scrollbar_get_offset(bw->scroll_y)) * + bw->scale; + break; + } + + bw = bw->parent; + + if (!root) { + /* return if we just wanted the position in the parent + * browser window. */ + return; + } + } +} + + +/* exported interface, documented in netsurf/browser_window.h */ +void browser_window_set_position(struct browser_window *bw, int x, int y) +{ + assert(bw != NULL); + + if (bw->window == NULL) { + /* Core managed browser window */ + bw->x = x; + bw->y = y; + } else { + NSLOG(netsurf, INFO, + "Asked to set position of front end window."); + assert(0); + } +} + + +/* exported interface, documented in netsurf/browser_window.h */ +void +browser_window_set_drag_type(struct browser_window *bw, + browser_drag_type type, + const struct rect *rect) +{ + struct browser_window *top_bw = browser_window_get_root(bw); + gui_drag_type gtype; + + bw->drag.type = type; + + if (type == DRAGGING_NONE) { + top_bw->drag.window = NULL; + } else { + top_bw->drag.window = bw; + + switch (type) { + case DRAGGING_SELECTION: + /** \todo tell front end */ + return; + case DRAGGING_SCR_X: + case DRAGGING_SCR_Y: + case DRAGGING_CONTENT_SCROLLBAR: + gtype = GDRAGGING_SCROLLBAR; + break; + default: + gtype = GDRAGGING_OTHER; + break; + } + + guit->window->drag_start(top_bw->window, gtype, rect); + } +} + + +/* exported interface, documented in netsurf/browser_window.h */ +browser_drag_type browser_window_get_drag_type(struct browser_window *bw) +{ + return bw->drag.type; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +struct browser_window * browser_window_get_root(struct browser_window *bw) +{ + while (bw && bw->parent) { + bw = bw->parent; + } + return bw; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +browser_editor_flags browser_window_get_editor_flags(struct browser_window *bw) +{ + browser_editor_flags ed_flags = BW_EDITOR_NONE; + assert(bw->window); + assert(bw->parent == NULL); + + if (bw->selection.bw != NULL) { + ed_flags |= BW_EDITOR_CAN_COPY; + + if (!bw->selection.read_only) + ed_flags |= BW_EDITOR_CAN_CUT; + } + + if (bw->can_edit) + ed_flags |= BW_EDITOR_CAN_PASTE; + + return ed_flags; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +bool browser_window_can_select(struct browser_window *bw) +{ + if (bw == NULL || bw->current_content == NULL) + return false; + + /* TODO: We shouldn't have to know about specific content types + * here. There should be a content_is_selectable() call. */ + if (content_get_type(bw->current_content) != CONTENT_HTML && + content_get_type(bw->current_content) != + CONTENT_TEXTPLAIN) + return false; + + return true; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +char * browser_window_get_selection(struct browser_window *bw) +{ + assert(bw->window); + assert(bw->parent == NULL); + + if (bw->selection.bw == NULL || + bw->selection.bw->current_content == NULL) + return NULL; + + return content_get_selection(bw->selection.bw->current_content); +} + + +/* exported interface, documented in netsurf/browser_window.h */ +bool browser_window_can_search(struct browser_window *bw) +{ + if (bw == NULL || bw->current_content == NULL) + return false; + + /** \todo We shouldn't have to know about specific content + * types here. There should be a content_is_searchable() call. + */ + if ((content_get_type(bw->current_content) != CONTENT_HTML) && + (content_get_type(bw->current_content) != CONTENT_TEXTPLAIN)) { + return false; + } + + return true; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +bool browser_window_is_frameset(struct browser_window *bw) +{ + return (bw->children != NULL); +} + + +/* exported interface, documented in netsurf/browser_window.h */ +nserror +browser_window_get_scrollbar_type(struct browser_window *bw, + browser_scrolling *h, + browser_scrolling *v) +{ + *h = bw->scrolling; + *v = bw->scrolling; + + return NSERROR_OK; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +nserror +browser_window_get_features(struct browser_window *bw, + int x, int y, + struct browser_window_features *data) +{ + /* clear the features structure to empty values */ + data->link = NULL; + data->object = NULL; + data->main = NULL; + data->form_features = CTX_FORM_NONE; + + return browser_window__get_contextual_content(bw, + x / bw->scale, + y / bw->scale, + data); +} + + +/* exported interface, documented in netsurf/browser_window.h */ +bool +browser_window_scroll_at_point(struct browser_window *bw, + int x, int y, + int scrx, int scry) +{ + return browser_window_scroll_at_point_internal(bw, + x / bw->scale, + y / bw->scale, + scrx, + scry); +} + + +/* exported interface, documented in netsurf/browser_window.h */ +bool +browser_window_drop_file_at_point(struct browser_window *bw, + int x, int y, + char *file) +{ + return browser_window_drop_file_at_point_internal(bw, + x / bw->scale, + y / bw->scale, + file); +} + + +/* exported interface, documented in netsurf/browser_window.h */ +void +browser_window_set_gadget_filename(struct browser_window *bw, + struct form_control *gadget, + const char *fn) +{ + html_set_file_gadget_filename(bw->current_content, gadget, fn); +} + + +/* exported interface, documented in netsurf/browser_window.h */ +nserror +browser_window_debug_dump(struct browser_window *bw, + FILE *f, + enum content_debug op) +{ + if (bw->current_content != NULL) { + return content_debug_dump(bw->current_content, f, op); + } + return NSERROR_OK; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +nserror browser_window_debug(struct browser_window *bw, enum content_debug op) +{ + if (bw->current_content != NULL) { + return content_debug(bw->current_content, op); + } + return NSERROR_OK; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +nserror +browser_window_create(enum browser_window_create_flags flags, + nsurl *url, + nsurl *referrer, + struct browser_window *existing, + struct browser_window **bw) +{ + gui_window_create_flags gw_flags = GW_CREATE_NONE; + struct browser_window *ret; + nserror err; + + /* Check parameters */ + if (flags & BW_CREATE_CLONE) { + if (existing == NULL) { + assert(0 && "Failed: No existing window provided."); + return NSERROR_BAD_PARAMETER; + } + } + + if (!(flags & BW_CREATE_HISTORY)) { + if (!(flags & BW_CREATE_CLONE) || existing == NULL) { + assert(0 && "Failed: Must have existing for history."); + return NSERROR_BAD_PARAMETER; + } + } + + ret = calloc(1, sizeof(struct browser_window)); + if (ret == NULL) { + return NSERROR_NOMEM; + } + + /* Initialise common parts */ + err = browser_window_initialise_common(flags, ret, existing); + if (err != NSERROR_OK) { + browser_window_destroy(ret); + return err; + } + + /* window characteristics */ + ret->browser_window_type = BROWSER_WINDOW_NORMAL; + ret->scrolling = BW_SCROLLING_YES; + ret->border = true; + ret->no_resize = true; + ret->focus = ret; + + /* initialise last action with creation time */ + nsu_getmonotonic_ms(&ret->last_action); + + /* The existing gui_window is on the top-level existing + * browser_window. */ + existing = browser_window_get_root(existing); + + /* Set up gui_window creation flags */ + if (flags & BW_CREATE_TAB) + gw_flags |= GW_CREATE_TAB; + if (flags & BW_CREATE_CLONE) + gw_flags |= GW_CREATE_CLONE; + if (flags & BW_CREATE_FOREGROUND) + gw_flags |= GW_CREATE_FOREGROUND; + if (flags & BW_CREATE_FOCUS_LOCATION) + gw_flags |= GW_CREATE_FOCUS_LOCATION; + + ret->window = guit->window->create(ret, + (existing != NULL) ? existing->window : NULL, + gw_flags); + + if (ret->window == NULL) { + browser_window_destroy(ret); + return NSERROR_BAD_PARAMETER; + } + + if (url != NULL) { + enum browser_window_nav_flags nav_flags; + nav_flags = BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE; + if (flags & BW_CREATE_UNVERIFIABLE) { + nav_flags |= BW_NAVIGATE_UNVERIFIABLE; + } + if (flags & BW_CREATE_HISTORY) { + nav_flags |= BW_NAVIGATE_HISTORY; + } + browser_window_navigate(ret, + url, + referrer, + nav_flags, + NULL, + NULL, + NULL); + } + + if (bw != NULL) { + *bw = ret; + } + + return NSERROR_OK; +} + + +/* exported internal interface, documented in desktop/browser_private.h */ +nserror +browser_window_initialise_common(enum browser_window_create_flags flags, + struct browser_window *bw, + const struct browser_window *existing) +{ + nserror err; + assert(bw); + + /* new javascript context for each window/(i)frame */ + err = js_newheap(nsoption_int(script_timeout), &bw->jsheap); + if (err != NSERROR_OK) + return err; + + if (flags & BW_CREATE_CLONE) { + assert(existing != NULL); + + /* clone history */ + err = browser_window_history_clone(existing, bw); + + /* copy the scale */ + bw->scale = existing->scale; + } else { + /* create history */ + err = browser_window_history_create(bw); + + /* default scale */ + bw->scale = (float) nsoption_int(scale) / 100.0; + } + + if (err != NSERROR_OK) + return err; + + /* window characteristics */ + bw->refresh_interval = -1; + + bw->drag.type = DRAGGING_NONE; + + bw->scroll_x = NULL; + bw->scroll_y = NULL; + + bw->focus = NULL; + + /* initialise status text cache */ + bw->status.text = NULL; + bw->status.text_len = 0; + bw->status.match = 0; + bw->status.miss = 0; + + return NSERROR_OK; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +void browser_window_destroy(struct browser_window *bw) +{ + /* can't destoy child windows on their own */ + assert(!bw->parent); + + /* destroy */ + browser_window_destroy_internal(bw); + free(bw); +} + + +/* exported interface, documented in netsurf/browser_window.h */ +nserror browser_window_refresh_url_bar(struct browser_window *bw) +{ + nserror ret; + nsurl *display_url, *url; + + assert(bw); + + if (bw->parent != NULL) { + /* Not root window; don't set a URL in GUI URL bar */ + return NSERROR_OK; + } + + if (bw->current_content == NULL) { + /* no content so return about:blank */ + ret = browser_window_refresh_url_bar_internal(bw, + corestring_nsurl_about_blank); + } else if (bw->throbbing && bw->loading_parameters.url != NULL) { + /* Throbbing and we have loading parameters, use those */ + url = bw->loading_parameters.url; + ret = browser_window_refresh_url_bar_internal(bw, url); + } else if (bw->frag_id == NULL) { + if (bw->internal_nav) { + url = bw->loading_parameters.url; + } else { + url = hlcache_handle_get_url(bw->current_content); + } + ret = browser_window_refresh_url_bar_internal(bw, url); + } else { + /* Combine URL and Fragment */ + if (bw->internal_nav) { + url = bw->loading_parameters.url; + } else { + url = hlcache_handle_get_url(bw->current_content); + } + ret = nsurl_refragment( + url, + bw->frag_id, &display_url); + if (ret == NSERROR_OK) { + ret = browser_window_refresh_url_bar_internal(bw, + display_url); + nsurl_unref(display_url); + } + } + + return ret; +} + + +/* exported interface documented in netsurf/browser_window.h */ +nserror +browser_window_navigate(struct browser_window *bw, + nsurl *url, + nsurl *referrer, + enum browser_window_nav_flags flags, + char *post_urlenc, + struct fetch_multipart_data *post_multipart, + hlcache_handle *parent) +{ + int depth = 0; + struct browser_window *cur; + uint32_t fetch_flags = 0; + bool fetch_is_post = (post_urlenc != NULL || post_multipart != NULL); + llcache_post_data post; + hlcache_child_context child; + nserror error; + bool is_internal = false; + struct browser_fetch_parameters params, *pass_params = NULL; + + assert(bw); + assert(url); + + NSLOG(netsurf, INFO, "bw %p, url %s", bw, nsurl_access(url)); + + /* + * determine if navigation is internal url, if so, we do not + * do certain things during the load. + */ + is_internal = is_internal_navigate_url(url); + + if (is_internal && + !(flags & BW_NAVIGATE_INTERNAL)) { + /* Internal navigation detected, but flag not set, only allow + * this is there's a fetch multipart + */ + if (post_multipart == NULL) { + return NSERROR_NEED_DATA; + } + /* It *is* internal, set it as such */ + flags |= BW_NAVIGATE_INTERNAL | BW_NAVIGATE_HISTORY; + /* If we were previously internal, don't update again */ + if (bw->internal_nav) { + flags |= BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE; + } + } + + /* If we're navigating and we have a history entry and a content + * then update the history entry before we navigate to save our + * current state. However since history navigation pre-moves + * the history state, we ensure that we only do this if we've not + * been suppressed. In the suppressed case, the history code + * updates the history itself before navigating. + */ + if (bw->current_content != NULL && + bw->history != NULL && + bw->history->current != NULL && + !is_internal && + !(flags & BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE)) { + browser_window_history_update(bw, bw->current_content); + } + + /* don't allow massively nested framesets */ + for (cur = bw; cur->parent; cur = cur->parent) { + depth++; + } + if (depth > FRAME_DEPTH) { + NSLOG(netsurf, INFO, "frame depth too high."); + return NSERROR_FRAME_DEPTH; + } + + /* Set up retrieval parameters */ + if (!(flags & BW_NAVIGATE_UNVERIFIABLE)) { + fetch_flags |= LLCACHE_RETRIEVE_VERIFIABLE; + } + + if (post_multipart != NULL) { + post.type = LLCACHE_POST_MULTIPART; + post.data.multipart = post_multipart; + } else if (post_urlenc != NULL) { + post.type = LLCACHE_POST_URL_ENCODED; + post.data.urlenc = post_urlenc; + } + + child.charset = content_get_encoding(parent, CONTENT_ENCODING_NORMAL); + if ((parent != NULL) && (content_get_type(parent) == CONTENT_HTML)) { + child.quirks = content_get_quirks(parent); + } else { + child.quirks = false; + } + + url = nsurl_ref(url); + + if (referrer != NULL) { + referrer = nsurl_ref(referrer); + } + + /* Get download out of the way */ + if ((flags & BW_NAVIGATE_DOWNLOAD) != 0) { + error = browser_window_download(bw, + url, + referrer, + fetch_flags, + fetch_is_post, + &post); + nsurl_unref(url); + if (referrer != NULL) { + nsurl_unref(referrer); + } + return error; + } + + if (bw->frag_id != NULL) { + lwc_string_unref(bw->frag_id); + } + bw->frag_id = NULL; + + if (nsurl_has_component(url, NSURL_FRAGMENT)) { + bool same_url = false; + + bw->frag_id = nsurl_get_component(url, NSURL_FRAGMENT); + + /* Compare new URL with existing one (ignoring fragments) */ + if ((bw->current_content != NULL) && + (hlcache_handle_get_url(bw->current_content) != NULL)) { + same_url = nsurl_compare( + url, + hlcache_handle_get_url(bw->current_content), + NSURL_COMPLETE); + } + + /* if we're simply moving to another ID on the same page, + * don't bother to fetch, just update the window. + */ + if ((same_url) && + (fetch_is_post == false) && + (nsurl_has_component(url, NSURL_QUERY) == false)) { + nsurl_unref(url); + + if (referrer != NULL) { + nsurl_unref(referrer); + } + + if ((flags & BW_NAVIGATE_HISTORY) != 0) { + browser_window_history_add(bw, + bw->current_content, + bw->frag_id); + } + + browser_window_update(bw, false); + + if (bw->current_content != NULL) { + browser_window_refresh_url_bar(bw); + } + return NSERROR_OK; + } + } + + browser_window_stop(bw); + browser_window_remove_caret(bw, false); + browser_window_destroy_children(bw); + browser_window_destroy_iframes(bw); + + /* Set up the fetch parameters */ + memset(¶ms, 0, sizeof(params)); + + params.url = nsurl_ref(url); + + if (referrer != NULL) { + params.referrer = nsurl_ref(referrer); + } + + params.flags = flags; + + if (post_urlenc != NULL) { + params.post_urlenc = strdup(post_urlenc); + } + + if (post_multipart != NULL) { + params.post_multipart = fetch_multipart_data_clone(post_multipart); + } + + if (parent != NULL) { + params.parent_charset = strdup(child.charset); + params.parent_quirks = child.quirks; + } + + bw->internal_nav = is_internal; + + if (is_internal) { + pass_params = ¶ms; + } else { + /* At this point, we're navigating, so store the fetch parameters */ + browser_window__free_fetch_parameters(&bw->loading_parameters); + memcpy(&bw->loading_parameters, ¶ms, sizeof(params)); + memset(¶ms, 0, sizeof(params)); + pass_params = &bw->loading_parameters; + } + + error = browser_window__navigate_internal(bw, pass_params); + + nsurl_unref(url); + + if (referrer != NULL) { + nsurl_unref(referrer); + } + + if (is_internal) { + browser_window__free_fetch_parameters(¶ms); + } + + return error; +} + + +/** + * Internal navigation handler for normal fetches + */ +static nserror +navigate_internal_real(struct browser_window *bw, + struct browser_fetch_parameters *params) +{ + uint32_t fetch_flags = 0; + bool fetch_is_post; + llcache_post_data post; + hlcache_child_context child; + nserror res; + hlcache_handle *c; + + NSLOG(netsurf, INFO, "Loading '%s'", nsurl_access(params->url)); + + fetch_is_post = (params->post_urlenc != NULL || params->post_multipart != NULL); + + /* Clear SSL info for load */ + cert_chain_free(bw->loading_cert_chain); + bw->loading_cert_chain = NULL; + + /* Set up retrieval parameters */ + if (!(params->flags & BW_NAVIGATE_UNVERIFIABLE)) { + fetch_flags |= LLCACHE_RETRIEVE_VERIFIABLE; + } + + if (params->post_multipart != NULL) { + post.type = LLCACHE_POST_MULTIPART; + post.data.multipart = params->post_multipart; + } else if (params->post_urlenc != NULL) { + post.type = LLCACHE_POST_URL_ENCODED; + post.data.urlenc = params->post_urlenc; + } + + if (params->parent_charset != NULL) { + child.charset = params->parent_charset; + child.quirks = params->parent_quirks; + } + + browser_window_set_status(bw, messages_get("Loading")); + bw->history_add = (params->flags & BW_NAVIGATE_HISTORY); + + /* Verifiable fetches may trigger a download */ + if (!(params->flags & BW_NAVIGATE_UNVERIFIABLE)) { + fetch_flags |= HLCACHE_RETRIEVE_MAY_DOWNLOAD; + } + + res = hlcache_handle_retrieve(params->url, + fetch_flags | HLCACHE_RETRIEVE_SNIFF_TYPE, + params->referrer, + fetch_is_post ? &post : NULL, + browser_window_callback, + bw, + params->parent_charset != NULL ? &child : NULL, + CONTENT_ANY, + &c); + + switch (res) { + case NSERROR_OK: + bw->loading_content = c; + browser_window_start_throbber(bw); + if (bw->window != NULL) { + guit->window->set_icon(bw->window, NULL); + } + if (bw->internal_nav == false) { + res = browser_window_refresh_url_bar_internal(bw, + params->url); + } + break; + + case NSERROR_NO_FETCH_HANDLER: /* no handler for this type */ + /** \todo does this always try and download even + * unverifiable content? + */ + res = guit->misc->launch_url(params->url); + break; + + default: /* report error to user */ + browser_window_set_status(bw, messages_get_errorcode(res)); + break; + + } + + /* Record time */ + nsu_getmonotonic_ms(&bw->last_action); + + return res; +} + + +/** + * Internal navigation handler for the authentication query handler + * + * If the parameters indicate we're processing a *response* from the handler + * then we deal with that, otherwise we pass it on to the about: handler + */ +static nserror +navigate_internal_query_auth(struct browser_window *bw, + struct browser_fetch_parameters *params) +{ + char *userpass = NULL; + const char *username, *password, *realm, *siteurl; + nsurl *sitensurl; + nserror res; + bool is_login = false, is_cancel = false; + + assert(params->post_multipart != NULL); + + is_login = fetch_multipart_data_find(params->post_multipart, "login") != NULL; + is_cancel = fetch_multipart_data_find(params->post_multipart, "cancel") != NULL; + + if (!(is_login || is_cancel)) { + /* This is a request, so pass it on */ + return navigate_internal_real(bw, params); + } + + if (is_cancel) { + /* We're processing a cancel, do a rough-and-ready nav to + * about:blank + */ + browser_window__free_fetch_parameters(&bw->loading_parameters); + bw->loading_parameters.url = nsurl_ref(corestring_nsurl_about_blank); + bw->loading_parameters.flags = BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE | BW_NAVIGATE_INTERNAL; + bw->internal_nav = true; + return browser_window__navigate_internal(bw, &bw->loading_parameters); + } + + /* We're processing a "login" attempt from the form */ + + /* Retrieve the data */ + username = fetch_multipart_data_find(params->post_multipart, "username"); + password = fetch_multipart_data_find(params->post_multipart, "password"); + realm = fetch_multipart_data_find(params->post_multipart, "realm"); + siteurl = fetch_multipart_data_find(params->post_multipart, "siteurl"); + + if (username == NULL || password == NULL || + realm == NULL || siteurl == NULL) { + /* Bad inputs, simply fail */ + return NSERROR_INVALID; + } + + /* Parse the URL */ + res = nsurl_create(siteurl, &sitensurl); + if (res != NSERROR_OK) { + return res; + } + + /* Construct the username/password */ + res = browser_window__build_userpass(username, password, &userpass); + if (res != NSERROR_OK) { + nsurl_unref(sitensurl); + return res; + } + + /* And let urldb know */ + urldb_set_auth_details(sitensurl, realm, userpass); + + /* Clean up */ + free(userpass); + nsurl_unref(sitensurl); + + /* Finally navigate to the original loading parameters */ + bw->internal_nav = false; + return navigate_internal_real(bw, &bw->loading_parameters); +} + + +/** + * Internal navigation handler for the SSL/privacy query page. + * + * If the parameters indicate we're processing a *response* from the handler + * then we deal with that, otherwise we pass it on to the about: handler + */ +static nserror +navigate_internal_query_ssl(struct browser_window *bw, + struct browser_fetch_parameters *params) +{ + bool is_proceed = false, is_back = false; + const char *siteurl = NULL; + nsurl *siteurl_ns; + + assert(params->post_multipart != NULL); + + is_proceed = fetch_multipart_data_find(params->post_multipart, "proceed") != NULL; + is_back = fetch_multipart_data_find(params->post_multipart, "back") != NULL; + siteurl = fetch_multipart_data_find(params->post_multipart, "siteurl"); + + if (!(is_proceed || is_back) || siteurl == NULL) { + /* This is a request, so pass it on */ + return navigate_internal_real(bw, params); + } + + if (nsurl_create(siteurl, &siteurl_ns) != NSERROR_OK) { + NSLOG(netsurf, ERROR, "Unable to reset ssl loading parameters"); + } else { + /* In order that we may proceed, replace the loading parameters */ + nsurl_unref(bw->loading_parameters.url); + bw->loading_parameters.url = siteurl_ns; + } + + return browser_window__handle_ssl_query_response(is_proceed, bw); +} + + +/** + * Internal navigation handler for the timeout query page. + * + * If the parameters indicate we're processing a *response* from the handler + * then we deal with that, otherwise we pass it on to the about: handler + */ +static nserror +navigate_internal_query_timeout(struct browser_window *bw, + struct browser_fetch_parameters *params) +{ + bool is_retry = false, is_back = false; + + NSLOG(netsurf, INFO, "bw:%p params:%p", bw, params); + + assert(params->post_multipart != NULL); + + is_retry = fetch_multipart_data_find(params->post_multipart, "retry") != NULL; + is_back = fetch_multipart_data_find(params->post_multipart, "back") != NULL; + + if (is_back) { + /* do a rough-and-ready nav to the old 'current' + * parameters, with any post data stripped away + */ + return browser_window__reload_current_parameters(bw); + } + + if (is_retry) { + /* Finally navigate to the original loading parameters */ + bw->internal_nav = false; + return navigate_internal_real(bw, &bw->loading_parameters); + } + + return navigate_internal_real(bw, params); +} + + +/** + * Internal navigation handler for the fetch error query page. + * + * If the parameters indicate we're processing a *response* from the handler + * then we deal with that, otherwise we pass it on to the about: handler + */ +static nserror +navigate_internal_query_fetcherror(struct browser_window *bw, + struct browser_fetch_parameters *params) +{ + bool is_retry = false, is_back = false; + + NSLOG(netsurf, INFO, "bw:%p params:%p", bw, params); + + assert(params->post_multipart != NULL); + + is_retry = fetch_multipart_data_find(params->post_multipart, "retry") != NULL; + is_back = fetch_multipart_data_find(params->post_multipart, "back") != NULL; + + if (is_back) { + /* do a rough-and-ready nav to the old 'current' + * parameters, with any post data stripped away + */ + return browser_window__reload_current_parameters(bw); + } + + if (is_retry) { + /* Finally navigate to the original loading parameters */ + bw->internal_nav = false; + return navigate_internal_real(bw, &bw->loading_parameters); + } + + return navigate_internal_real(bw, params); +} + + +/** + * dispatch to internal query handlers or normal navigation + * + * Here we determine if we're navigating to an internal query URI and + * if so, what we need to do about it. + * + * \note these check must match those in is_internal_navigate_url() + * + * If we're not, then we just move on to the real navigate. + */ +nserror +browser_window__navigate_internal(struct browser_window *bw, + struct browser_fetch_parameters *params) +{ + lwc_string *scheme, *path; + + /* All our special URIs are in the about: scheme */ + scheme = nsurl_get_component(params->url, NSURL_SCHEME); + if (scheme != corestring_lwc_about) { + lwc_string_unref(scheme); + goto normal_fetch; + } + lwc_string_unref(scheme); + + /* Is it the auth query handler? */ + path = nsurl_get_component(params->url, NSURL_PATH); + if (path == corestring_lwc_query_auth) { + lwc_string_unref(path); + return navigate_internal_query_auth(bw, params); + } + if (path == corestring_lwc_query_ssl) { + lwc_string_unref(path); + return navigate_internal_query_ssl(bw, params); + } + if (path == corestring_lwc_query_timeout) { + lwc_string_unref(path); + return navigate_internal_query_timeout(bw, params); + } + if (path == corestring_lwc_query_fetcherror) { + lwc_string_unref(path); + return navigate_internal_query_fetcherror(bw, params); + } + if (path != NULL) { + lwc_string_unref(path); + } + + /* Fall through to a normal about: fetch */ + + normal_fetch: + return navigate_internal_real(bw, params); +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +bool browser_window_up_available(struct browser_window *bw) +{ + bool result = false; + + if (bw != NULL && bw->current_content != NULL) { + nsurl *parent; + nserror err; + err = nsurl_parent(hlcache_handle_get_url(bw->current_content), + &parent); + if (err == NSERROR_OK) { + result = nsurl_compare(hlcache_handle_get_url( + bw->current_content), + parent, + NSURL_COMPLETE) == false; + nsurl_unref(parent); + } + } + + return result; +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +nserror browser_window_navigate_up(struct browser_window *bw, bool new_window) +{ + nsurl *current, *parent; + nserror err; + + if (bw == NULL) + return NSERROR_BAD_PARAMETER; + + current = browser_window_access_url(bw); + + err = nsurl_parent(current, &parent); + if (err != NSERROR_OK) { + return err; + } + + if (nsurl_compare(current, parent, NSURL_COMPLETE) == true) { + /* Can't go up to parent from here */ + nsurl_unref(parent); + return NSERROR_OK; + } + + if (new_window) { + err = browser_window_create(BW_CREATE_CLONE, + parent, NULL, bw, NULL); + } else { + err = browser_window_navigate(bw, parent, NULL, + BW_NAVIGATE_HISTORY, + NULL, NULL, NULL); + } + + nsurl_unref(parent); + return err; +} + + +/* Exported interface, documented in include/netsurf/browser_window.h */ +nsurl* browser_window_access_url(const struct browser_window *bw) +{ + assert(bw != NULL); + + if (bw->current_content != NULL) { + return hlcache_handle_get_url(bw->current_content); + + } else if (bw->loading_content != NULL) { + /* TODO: should we return this? */ + return hlcache_handle_get_url(bw->loading_content); + } + + return corestring_nsurl_about_blank; +} + + +/* Exported interface, documented in include/netsurf/browser_window.h */ +nserror +browser_window_get_url(struct browser_window *bw, bool fragment,nsurl** url_out) +{ + nserror err; + nsurl *url; + + assert(bw != NULL); + + if (!fragment || bw->frag_id == NULL || bw->loading_content != NULL) { + /* If there's a loading content, then the bw->frag_id will have + * been trampled, possibly with a new frag_id, but we will + * still be returning the current URL, so in this edge case + * we just drop any fragment. */ + url = nsurl_ref(browser_window_access_url(bw)); + + } else { + err = nsurl_refragment(browser_window_access_url(bw), + bw->frag_id, &url); + if (err != NSERROR_OK) { + return err; + } + } + + *url_out = url; + return NSERROR_OK; +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +const char* browser_window_get_title(struct browser_window *bw) +{ + assert(bw != NULL); + + if (bw->current_content != NULL) { + return content_get_title(bw->current_content); + } + + /* no content so return about:blank */ + return nsurl_access(corestring_nsurl_about_blank); +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +struct history * browser_window_get_history(struct browser_window *bw) +{ + assert(bw != NULL); + + return bw->history; +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +bool browser_window_has_content(struct browser_window *bw) +{ + assert(bw != NULL); + + if (bw->current_content == NULL) { + return false; + } + + return true; +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +struct hlcache_handle *browser_window_get_content(struct browser_window *bw) +{ + return bw->current_content; +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +nserror browser_window_get_extents(struct browser_window *bw, bool scaled, + int *width, int *height) +{ + assert(bw != NULL); + + if (bw->current_content == NULL) { + *width = 0; + *height = 0; + return NSERROR_BAD_CONTENT; + } + + *width = content_get_width(bw->current_content); + *height = content_get_height(bw->current_content); + + if (scaled) { + *width *= bw->scale; + *height *= bw->scale; + } + + return NSERROR_OK; +} + + +/* exported internal interface, documented in desktop/browser_private.h */ +nserror +browser_window_get_dimensions(struct browser_window *bw, + int *width, + int *height) +{ + nserror res; + assert(bw); + + if (bw->window == NULL) { + /* Core managed browser window */ + *width = bw->width; + *height = bw->height; + res = NSERROR_OK; + } else { + /* Front end window */ + res = guit->window->get_dimensions(bw->window, width, height); + } + return res; +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +void +browser_window_set_dimensions(struct browser_window *bw, int width, int height) +{ + assert(bw); + + if (bw->window == NULL) { + /* Core managed browser window */ + bw->width = width; + bw->height = height; + } else { + NSLOG(netsurf, INFO, + "Asked to set dimensions of front end window."); + assert(0); + } +} + + +/* Exported interface, documented in browser/browser_private.h */ +nserror +browser_window_invalidate_rect(struct browser_window *bw, struct rect *rect) +{ + int pos_x; + int pos_y; + struct browser_window *top = bw; + + assert(bw); + + if (bw->window == NULL) { + /* Core managed browser window */ + browser_window_get_position(bw, true, &pos_x, &pos_y); + + top = browser_window_get_root(bw); + + rect->x0 += pos_x / bw->scale; + rect->y0 += pos_y / bw->scale; + rect->x1 += pos_x / bw->scale; + rect->y1 += pos_y / bw->scale; + } + + rect->x0 *= top->scale; + rect->y0 *= top->scale; + rect->x1 *= top->scale; + rect->y1 *= top->scale; + + return guit->window->invalidate(top->window, rect); +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +void browser_window_stop(struct browser_window *bw) +{ + int children, index; + + if (bw->loading_content != NULL) { + hlcache_handle_abort(bw->loading_content); + hlcache_handle_release(bw->loading_content); + bw->loading_content = NULL; + } + + if (bw->current_content != NULL && + content_get_status(bw->current_content) != CONTENT_STATUS_DONE) { + nserror error; + assert(content_get_status(bw->current_content) == + CONTENT_STATUS_READY); + error = hlcache_handle_abort(bw->current_content); + assert(error == NSERROR_OK); + } + + guit->misc->schedule(-1, browser_window_refresh, bw); + + if (bw->children) { + children = bw->rows * bw->cols; + for (index = 0; index < children; index++) + browser_window_stop(&bw->children[index]); + } + if (bw->iframes) { + children = bw->iframe_count; + for (index = 0; index < children; index++) + browser_window_stop(&bw->iframes[index]); + } + + if (bw->current_content != NULL) { + browser_window_refresh_url_bar(bw); + } + + browser_window_stop_throbber(bw); +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +nserror browser_window_reload(struct browser_window *bw, bool all) +{ + hlcache_handle *c; + unsigned int i; + struct nsurl *reload_url; + + if ((bw->current_content) == NULL || + (bw->loading_content) != NULL) { + return NSERROR_INVALID; + } + + if (all && content_get_type(bw->current_content) == CONTENT_HTML) { + struct html_stylesheet *sheets; + struct content_html_object *object; + unsigned int count; + + c = bw->current_content; + + /* invalidate objects */ + object = html_get_objects(c, &count); + + for (; object != NULL; object = object->next) { + if (object->content != NULL) + content_invalidate_reuse_data(object->content); + } + + /* invalidate stylesheets */ + sheets = html_get_stylesheets(c, &count); + + for (i = STYLESHEET_START; i != count; i++) { + if (sheets[i].sheet != NULL) { + content_invalidate_reuse_data(sheets[i].sheet); + } + } + } + + content_invalidate_reuse_data(bw->current_content); + + reload_url = hlcache_handle_get_url(bw->current_content); + + return browser_window_navigate(bw, + reload_url, + NULL, + BW_NAVIGATE_NONE, + NULL, + NULL, + NULL); +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +void browser_window_set_status(struct browser_window *bw, const char *text) +{ + int text_len; + /* find topmost window */ + while (bw->parent) + bw = bw->parent; + + if ((bw->status.text != NULL) && + (strcmp(text, bw->status.text) == 0)) { + /* status text is unchanged */ + bw->status.match++; + return; + } + + /* status text is changed */ + + text_len = strlen(text); + + if ((bw->status.text == NULL) || (bw->status.text_len < text_len)) { + /* no current string allocation or it is not long enough */ + free(bw->status.text); + bw->status.text = strdup(text); + bw->status.text_len = text_len; + } else { + /* current allocation has enough space */ + memcpy(bw->status.text, text, text_len + 1); + } + + bw->status.miss++; + guit->window->set_status(bw->window, bw->status.text); +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +void browser_window_set_pointer(struct browser_window *bw, + browser_pointer_shape shape) +{ + struct browser_window *root = browser_window_get_root(bw); + gui_pointer_shape gui_shape; + bool loading; + uint64_t ms_now; + + assert(root); + assert(root->window); + + loading = ((bw->loading_content != NULL) || + ((bw->current_content != NULL) && + (content_get_status(bw->current_content) == CONTENT_STATUS_READY))); + + nsu_getmonotonic_ms(&ms_now); + + if (loading && ((ms_now - bw->last_action) < 1000)) { + /* If loading and less than 1 second since last link followed, + * force progress indicator pointer */ + gui_shape = GUI_POINTER_PROGRESS; + + } else if (shape == BROWSER_POINTER_AUTO) { + /* Up to browser window to decide */ + if (loading) { + gui_shape = GUI_POINTER_PROGRESS; + } else { + gui_shape = GUI_POINTER_DEFAULT; + } + + } else { + /* Use what we were told */ + gui_shape = (gui_pointer_shape)shape; + } + + guit->window->set_pointer(root->window, gui_shape); +} + + +/* exported function documented in netsurf/browser_window.h */ +nserror browser_window_schedule_reformat(struct browser_window *bw) +{ + if (bw->window == NULL) { + return NSERROR_BAD_PARAMETER; + } + + return guit->misc->schedule(0, scheduled_reformat, bw); +} + + +/* exported function documented in netsurf/browser_window.h */ +void +browser_window_reformat(struct browser_window *bw, + bool background, + int width, + int height) +{ + hlcache_handle *c = bw->current_content; + + if (c == NULL) + return; + + if (bw->browser_window_type != BROWSER_WINDOW_IFRAME) { + /* Iframe dimensions are already scaled in parent's layout */ + width /= bw->scale; + height /= bw->scale; + } + + if (bw->window == NULL) { + /* Core managed browser window; subtract scrollbar width */ + width -= bw->scroll_y ? SCROLLBAR_WIDTH : 0; + height -= bw->scroll_x ? SCROLLBAR_WIDTH : 0; + + width = width > 0 ? width : 0; + height = height > 0 ? height : 0; + } + + content_reformat(c, background, width, height); +} + + +/* exported interface documented in netsurf/browser_window.h */ +nserror +browser_window_set_scale(struct browser_window *bw, float scale, bool absolute) +{ + nserror res; + + /* get top browser window */ + while (bw->parent) { + bw = bw->parent; + } + + if (!absolute) { + /* snap small values around 1.0 */ + if ((scale + bw->scale) > (1.01 - scale) && + (scale + bw->scale) < (0.99 + scale)) { + scale = 1.0; + } else { + scale += bw->scale; + } + } + + /* clamp range between 0.1 and 10 (10% and 1000%) */ + if (scale < SCALE_MINIMUM) { + scale = SCALE_MINIMUM; + } else if (scale > SCALE_MAXIMUM) { + scale = SCALE_MAXIMUM; + } + + res = browser_window_set_scale_internal(bw, scale); + if (res == NSERROR_OK) { + browser_window_recalculate_frameset(bw); + } + + return res; +} + + +/* exported interface documented in netsurf/browser_window.h */ +float browser_window_get_scale(struct browser_window *bw) +{ + if (bw == NULL) { + return 1.0; + } + + return bw->scale; +} + + +/* exported interface documented in netsurf/browser_window.h */ +struct browser_window * +browser_window_find_target(struct browser_window *bw, + const char *target, + browser_mouse_state mouse) +{ + struct browser_window *bw_target; + struct browser_window *top; + hlcache_handle *c; + int rdepth; + nserror error; + + /* use the base target if we don't have one */ + c = bw->current_content; + if (target == NULL && + c != NULL && + content_get_type(c) == CONTENT_HTML) { + target = html_get_base_target(c); + } + if (target == NULL) { + target = "_self"; + } + + /* allow the simple case of target="_blank" to be ignored if requested + */ + if ((!(mouse & BROWSER_MOUSE_CLICK_2)) && + (!((mouse & BROWSER_MOUSE_CLICK_2) && + (mouse & BROWSER_MOUSE_MOD_2))) && + (!nsoption_bool(target_blank))) { + /* not a mouse button 2 click + * not a mouse button 1 click with ctrl pressed + * configured to ignore target="_blank" */ + if (!strcasecmp(target, "_blank")) + return bw; + } + + /* handle reserved keywords */ + if (((nsoption_bool(button_2_tab)) && + (mouse & BROWSER_MOUSE_CLICK_2))|| + ((!nsoption_bool(button_2_tab)) && + ((mouse & BROWSER_MOUSE_CLICK_1) && + (mouse & BROWSER_MOUSE_MOD_2))) || + ((nsoption_bool(button_2_tab)) && + (!strcasecmp(target, "_blank")))) { + /* open in new tab if: + * - button_2 opens in new tab and button_2 was pressed + * OR + * - button_2 doesn't open in new tabs and button_1 was + * pressed with ctrl held + * OR + * - button_2 opens in new tab and the link target is "_blank" + */ + error = browser_window_create(BW_CREATE_TAB | + BW_CREATE_HISTORY | + BW_CREATE_CLONE, + NULL, + NULL, + bw, + &bw_target); + if (error != NSERROR_OK) { + return bw; + } + return bw_target; + } else if (((!nsoption_bool(button_2_tab)) && + (mouse & BROWSER_MOUSE_CLICK_2)) || + ((nsoption_bool(button_2_tab)) && + ((mouse & BROWSER_MOUSE_CLICK_1) && + (mouse & BROWSER_MOUSE_MOD_2))) || + ((!nsoption_bool(button_2_tab)) && + (!strcasecmp(target, "_blank")))) { + /* open in new window if: + * - button_2 doesn't open in new tabs and button_2 was pressed + * OR + * - button_2 opens in new tab and button_1 was pressed with + * ctrl held + * OR + * - button_2 doesn't open in new tabs and the link target is + * "_blank" + */ + error = browser_window_create(BW_CREATE_HISTORY | + BW_CREATE_CLONE, + NULL, + NULL, + bw, + &bw_target); + if (error != NSERROR_OK) { + return bw; + } + return bw_target; + } else if (!strcasecmp(target, "_self")) { + return bw; + } else if (!strcasecmp(target, "_parent")) { + if (bw->parent) + return bw->parent; + return bw; + } else if (!strcasecmp(target, "_top")) { + while (bw->parent) + bw = bw->parent; + return bw; + } + + /* find frame according to B.8, ie using the following priorities: + * + * 1) current frame + * 2) closest to front + */ + rdepth = -1; + bw_target = NULL; + for (top = bw; top->parent; top = top->parent); + browser_window_find_target_internal(top, target, 0, bw, &rdepth, + &bw_target); + if (bw_target) + return bw_target; + + /* we require a new window using the target name */ + if (!nsoption_bool(target_blank)) + return bw; + + error = browser_window_create(BW_CREATE_CLONE | BW_CREATE_HISTORY, + NULL, + NULL, + bw, + &bw_target); + if (error != NSERROR_OK) { + return bw; + } + + /* frame names should begin with an alphabetic character (a-z,A-Z), + * however in practice you get things such as '_new' and '2left'. The + * only real effect this has is when giving out names as it can be + * assumed that an author intended '_new' to create a new nameless + * window (ie '_blank') whereas in the case of '2left' the intention + * was for a new named window. As such we merely special case windows + * that begin with an underscore. */ + if (target[0] != '_') { + bw_target->name = strdup(target); + } + return bw_target; +} + + +/* exported interface documented in netsurf/browser_window.h */ +void +browser_window_mouse_track(struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + browser_window_mouse_track_internal(bw, + mouse, + x / bw->scale, + y / bw->scale); +} + +/* exported interface documented in netsurf/browser_window.h */ +void +browser_window_mouse_click(struct browser_window *bw, + browser_mouse_state mouse, + int x, int y) +{ + browser_window_mouse_click_internal(bw, + mouse, + x / bw->scale, + y / bw->scale); +} + + +/* exported interface documented in netsurf/browser_window.h */ +void browser_window_page_drag_start(struct browser_window *bw, int x, int y) +{ + assert(bw != NULL); + + browser_window_set_drag_type(bw, DRAGGING_PAGE_SCROLL, NULL); + + bw->drag.start_x = x; + bw->drag.start_y = y; + + if (bw->window != NULL) { + /* Front end window */ + guit->window->get_scroll(bw->window, + &bw->drag.start_scroll_x, + &bw->drag.start_scroll_y); + + guit->window->event(bw->window, GW_EVENT_SCROLL_START); + } else { + /* Core managed browser window */ + bw->drag.start_scroll_x = scrollbar_get_offset(bw->scroll_x); + bw->drag.start_scroll_y = scrollbar_get_offset(bw->scroll_y); + } +} + + +/* exported interface documented in netsurf/browser_window.h */ +bool browser_window_back_available(struct browser_window *bw) +{ + if (bw != NULL && bw->internal_nav) { + /* Internal nav, back is possible */ + return true; + } + return (bw && bw->history && browser_window_history_back_available(bw)); +} + + +/* exported interface documented in netsurf/browser_window.h */ +bool browser_window_forward_available(struct browser_window *bw) +{ + return (bw && bw->history && browser_window_history_forward_available(bw)); +} + +/* exported interface documented in netsurf/browser_window.h */ +bool browser_window_reload_available(struct browser_window *bw) +{ + return (bw && bw->current_content && !bw->loading_content); +} + + +/* exported interface documented in netsurf/browser_window.h */ +bool browser_window_stop_available(struct browser_window *bw) +{ + return (bw && (bw->loading_content || + (bw->current_content && + (content_get_status(bw->current_content) != + CONTENT_STATUS_DONE)))); +} + +/* exported interface documented in netsurf/browser_window.h */ +bool +browser_window_exec(struct browser_window *bw, const char *src, size_t srclen) +{ + assert(bw != NULL); + + if (!bw->current_content) { + NSLOG(netsurf, DEEPDEBUG, "Unable to exec, no content"); + return false; + } + + if (content_get_status(bw->current_content) != CONTENT_STATUS_DONE) { + NSLOG(netsurf, DEEPDEBUG, "Unable to exec, content not done"); + return false; + } + + /* Okay it should be safe, forward the request through to the content + * itself. Only HTML contents currently support executing code + */ + return content_exec(bw->current_content, src, srclen); +} + + +/* exported interface documented in browser_window.h */ +nserror +browser_window_console_log(struct browser_window *bw, + browser_window_console_source src, + const char *msg, + size_t msglen, + browser_window_console_flags flags) +{ + browser_window_console_flags log_level = flags & BW_CS_FLAG_LEVEL_MASK; + struct browser_window *root = browser_window_get_root(bw); + + assert(msg != NULL); + /* We don't assert msglen > 0, if someone wants to log a real empty + * string then we won't stop them. It does sometimes happen from + * JavaScript for example. + */ + + /* bw is the target of the log, but root is where we log it */ + + NSLOG(netsurf, DEEPDEBUG, "Logging message in %p targetted at %p", root, bw); + NSLOG(netsurf, DEEPDEBUG, "Log came from %s", + ((src == BW_CS_INPUT) ? "user input" : + (src == BW_CS_SCRIPT_ERROR) ? "script error" : + (src == BW_CS_SCRIPT_CONSOLE) ? "script console" : + "unknown input location")); + + switch (log_level) { + case BW_CS_FLAG_LEVEL_DEBUG: + NSLOG(netsurf, DEBUG, "%.*s", (int)msglen, msg); + break; + case BW_CS_FLAG_LEVEL_LOG: + NSLOG(netsurf, VERBOSE, "%.*s", (int)msglen, msg); + break; + case BW_CS_FLAG_LEVEL_INFO: + NSLOG(netsurf, INFO, "%.*s", (int)msglen, msg); + break; + case BW_CS_FLAG_LEVEL_WARN: + NSLOG(netsurf, WARNING, "%.*s", (int)msglen, msg); + break; + case BW_CS_FLAG_LEVEL_ERROR: + NSLOG(netsurf, ERROR, "%.*s", (int)msglen, msg); + break; + default: + /* Unreachable */ + break; + } + + guit->window->console_log(root->window, src, msg, msglen, flags); + + return NSERROR_OK; +} + + +/* Exported interface, documented in browser_private.h */ +nserror +browser_window__reload_current_parameters(struct browser_window *bw) +{ + assert(bw != NULL); + + if (bw->current_parameters.post_urlenc != NULL) { + free(bw->current_parameters.post_urlenc); + bw->current_parameters.post_urlenc = NULL; + } + + if (bw->current_parameters.post_multipart != NULL) { + fetch_multipart_data_destroy(bw->current_parameters.post_multipart); + bw->current_parameters.post_multipart = NULL; + } + + if (bw->current_parameters.url == NULL) { + /* We have never navigated so go to about:blank */ + bw->current_parameters.url = nsurl_ref(corestring_nsurl_about_blank); + } + + bw->current_parameters.flags &= ~BW_NAVIGATE_HISTORY; + bw->internal_nav = false; + + browser_window__free_fetch_parameters(&bw->loading_parameters); + memcpy(&bw->loading_parameters, &bw->current_parameters, sizeof(bw->loading_parameters)); + memset(&bw->current_parameters, 0, sizeof(bw->current_parameters)); + return browser_window__navigate_internal(bw, &bw->loading_parameters); +} + +/* Exported interface, documented in browser_window.h */ +browser_window_page_info_state browser_window_get_page_info_state( + const struct browser_window *bw) +{ + lwc_string *scheme; + bool match; + + assert(bw != NULL); + + /* Do we have any content? If not -- UNKNOWN */ + if (bw->current_content == NULL) { + return PAGE_STATE_UNKNOWN; + } + + scheme = nsurl_get_component( + hlcache_handle_get_url(bw->current_content), NSURL_SCHEME); + + /* Is this an internal scheme? */ + if ((lwc_string_isequal(scheme, corestring_lwc_about, + &match) == lwc_error_ok && + (match == true)) || + (lwc_string_isequal(scheme, corestring_lwc_data, + &match) == lwc_error_ok && + (match == true)) || + (lwc_string_isequal(scheme, corestring_lwc_resource, + &match) == lwc_error_ok && + (match == true))) { + lwc_string_unref(scheme); + return PAGE_STATE_INTERNAL; + } + + /* Is this file:/// ? */ + if (lwc_string_isequal(scheme, corestring_lwc_file, + &match) == lwc_error_ok && + match == true) { + lwc_string_unref(scheme); + return PAGE_STATE_LOCAL; + } + + /* If not https, from here on down that'd be insecure */ + if ((lwc_string_isequal(scheme, corestring_lwc_https, + &match) == lwc_error_ok && + (match == false))) { + /* Some remote content, not https, therefore insecure */ + lwc_string_unref(scheme); + return PAGE_STATE_INSECURE; + } + + lwc_string_unref(scheme); + + /* Did we have to override this SSL setting? */ + if (urldb_get_cert_permissions(hlcache_handle_get_url(bw->current_content))) { + return PAGE_STATE_SECURE_OVERRIDE; + } + + /* If we've seen insecure content internally then we need to say so */ + if (content_saw_insecure_objects(bw->current_content)) { + return PAGE_STATE_SECURE_ISSUES; + } + + /* All is well, return secure state */ + return PAGE_STATE_SECURE; +} + +/* Exported interface, documented in browser_window.h */ +nserror +browser_window_get_ssl_chain(struct browser_window *bw, + struct cert_chain **chain) +{ + assert(bw != NULL); + + if (bw->current_cert_chain == NULL) { + return NSERROR_NOT_FOUND; + } + + *chain = bw->current_cert_chain; + + return NSERROR_OK; +} + +/* Exported interface, documented in browser_window.h */ +int browser_window_get_cookie_count( + const struct browser_window *bw) +{ + int count = 0; + char *cookies = urldb_get_cookie(browser_window_access_url(bw), true); + if (cookies == NULL) { + return 0; + } + + for (char *c = cookies; *c != '\0'; c++) { + if (*c == ';') + count++; + } + + free(cookies); + + return count; +} + +/* Exported interface, documented in browser_window.h */ +nserror browser_window_show_cookies( + const struct browser_window *bw) +{ + nserror err; + nsurl *url = browser_window_access_url(bw); + lwc_string *host = nsurl_get_component(url, NSURL_HOST); + const char *string = (host != NULL) ? lwc_string_data(host) : NULL; + + err = guit->misc->present_cookies(string); + + if (host != NULL) { + lwc_string_unref(host); + } + return err; +} + +/* Exported interface, documented in browser_window.h */ +nserror browser_window_show_certificates(struct browser_window *bw) +{ + nserror res; + nsurl *url; + + if (bw->current_cert_chain == NULL) { + return NSERROR_NOT_FOUND; + } + + res = cert_chain_to_query(bw->current_cert_chain, &url); + if (res == NSERROR_OK) { + res = browser_window_create(BW_CREATE_HISTORY | + BW_CREATE_FOREGROUND | + BW_CREATE_TAB, + url, + NULL, + bw, + NULL); + + nsurl_unref(url); + } + + return res; +} diff --git a/desktop/cookie_manager.c b/desktop/cookie_manager.c index a2aab8e9f..580750805 100644 --- a/desktop/cookie_manager.c +++ b/desktop/cookie_manager.c @@ -232,10 +232,6 @@ cookie_manager_field_builder(enum cookie_manager_field field, * * The time should be converted to text in the users locacle * - * \todo This should probably generate the user text using localtime - * and strftime with the c format specifier. Currently ctime will - * always generate output in the C locale. - * * \param field Cookie manager treeview field to build * \param fdata Cookie manager entry field data to set * \param value Time to show in field @@ -246,22 +242,20 @@ cookie_manager_field_builder_time(enum cookie_manager_field field, struct treeview_field_data *fdata, const time_t *value) { - const char *date; - char *date2; + struct tm *ftime; fdata->field = cm_ctx.fields[field].field; - - date = ctime(value); - date2 = strdup(date); - if (date2 == NULL) { - fdata->value = NULL; - fdata->value_len = 0; - } else { - assert(date2[24] == '\n'); - date2[24] = '\0'; - - fdata->value = date2; - fdata->value_len = strlen(date2); + fdata->value = NULL; + fdata->value_len = 0; + + if ((ftime = localtime(value)) != NULL) { + const size_t vsize = 256; + char *value = malloc(vsize); + if (value != NULL) { + fdata->value = value; + fdata->value_len = strftime(value, vsize, + "%a %b %e %H:%M:%S %Y", ftime); + } } return NSERROR_OK; @@ -545,6 +539,19 @@ void cookie_manager_remove(const struct cookie_data *data) } +/* exported interface documented in cookie_manager.h */ +nserror cookie_manager_set_search_string( + const char *string) +{ + /* If we don't have a cookie manager at the moment, just return */ + if (cm_ctx.tree == NULL) { + return NSERROR_NOT_FOUND; + } + + return treeview_set_search_string(cm_ctx.tree, string); +} + + /** * Initialise the treeview entry feilds * @@ -781,8 +788,7 @@ struct treeview_callback_table cm_tree_cb_t = { /* Exported interface, documented in cookie_manager.h */ -nserror cookie_manager_init(struct core_window_callback_table *cw_t, - void *core_window_handle) +nserror cookie_manager_init(void *core_window_handle) { nserror err; @@ -810,7 +816,7 @@ nserror cookie_manager_init(struct core_window_callback_table *cw_t, /* Create the cookie manager treeview */ err = treeview_create(&cm_ctx.tree, &cm_tree_cb_t, COOKIE_M_N_FIELDS, cm_ctx.fields, - cw_t, core_window_handle, + core_window_handle, TREEVIEW_NO_MOVES | TREEVIEW_DEL_EMPTY_DIRS | TREEVIEW_SEARCHABLE); diff --git a/desktop/cookie_manager.h b/desktop/cookie_manager.h index 4ae74a25f..532030b03 100644 --- a/desktop/cookie_manager.h +++ b/desktop/cookie_manager.h @@ -31,7 +31,6 @@ struct redraw_context; struct cookie_data; -struct core_window_callback_table; struct rect; /** @@ -42,12 +41,10 @@ struct rect; * * This must be called before any other cookie_manager_* function. * - * \param cw_t Callback table for core_window containing the treeview - * \param core_window_handle The core_window in which the treeview is shown + * \param core_window_handle The core_window in which the cookie view is shown * \return NSERROR_OK on success, appropriate error otherwise */ -nserror cookie_manager_init(struct core_window_callback_table *cw_t, - void *core_window_handle); +nserror cookie_manager_init(void *core_window_handle); /** * Finalise the cookie manager. @@ -76,6 +73,15 @@ bool cookie_manager_add(const struct cookie_data *data); void cookie_manager_remove(const struct cookie_data *data); /** + * Set the cookie manager search string. + * + * \param string Sering to set as search string. + * \return NSERROR_OK on success, appropriate error otherwise + */ +nserror cookie_manager_set_search_string( + const char *string); + +/** * Redraw the cookies manager. * * \param x X coordinate to render treeview at diff --git a/desktop/cw_helper.c b/desktop/cw_helper.c new file mode 100644 index 000000000..0e3cef74e --- /dev/null +++ b/desktop/cw_helper.c @@ -0,0 +1,86 @@ +/* + * Copyright 2019 Daniel Silverstone <dsilvers@netsurf-browser.org> + * Copyright 2019 Michael Drake <tlsa@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * + * Helpers to simplify core use of corewindow. + */ + +#include "utils/errors.h" +#include "netsurf/browser.h" +#include "netsurf/core_window.h" +#include "netsurf/types.h" +#include "css/utils.h" +#include "desktop/cw_helper.h" +#include "desktop/gui_internal.h" + +/* exported interface documented in cw_helper.h */ +nserror cw_helper_scroll_visible(struct core_window *cw_h, const struct rect *r) +{ + nserror err; + int height; + int width; + int x0; + int y0; + int x1; + int y1; + + assert(cw_h != NULL); + + err = guit->corewindow->get_dimensions(cw_h, &width, &height); + if (err != NSERROR_OK) { + return err; + } + + guit->corewindow->get_scroll(cw_h, &x0, &y0); + if (err != NSERROR_OK) { + return err; + } + + y1 = y0 + height; + x1 = x0 + width; + + if (r->y1 > y1) { + /* The bottom of the rectangle is off the bottom of the + * window, so scroll down to fit it + */ + y0 = r->y1 - height; + } + if (r->y0 < y0) { + /* The top of the rectangle is off the top of the window, + * so scroll up to fit it + */ + y0 = r->y0; + } + if (r->x1 > x1) { + /* The right of the rectangle is off the right of the window + * so scroll right to fit it + */ + x0 = r->x1 - width; + } + if (r->x0 < x0) { + /* The left of the rectangle is off the left of the window + * so scroll left to fit it + */ + x0 = r->x0; + } + + return guit->corewindow->set_scroll(cw_h, x0, y0); +} diff --git a/desktop/cw_helper.h b/desktop/cw_helper.h new file mode 100644 index 000000000..e8070ab06 --- /dev/null +++ b/desktop/cw_helper.h @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Michael Drake <tlsa@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * + * Helpers to simplify core use of corewindow. + */ + +#ifndef NETSURF_DESKTOP_CW_HELPER_H_ +#define NETSURF_DESKTOP_CW_HELPER_H_ + +struct rect; +struct core_window; + +/** + * Scroll a core window to make the given rectangle visible. + * + * \param[in] cw_t The core window callback table to use. + * \param[in] cw_h The core window's handle. + * \param[in] r The rectangle to make visisble by scrolling. + * \return NSERROR_OK on success or appropriate error code + */ +nserror cw_helper_scroll_visible(struct core_window *cw_h, const struct rect *r); + +#endif diff --git a/desktop/download.c b/desktop/download.c index 76c294977..4931371fb 100644 --- a/desktop/download.c +++ b/desktop/download.c @@ -29,6 +29,7 @@ #include "utils/corestrings.h" #include "utils/http.h" #include "utils/utils.h" +#include "utils/log.h" #include "desktop/download.h" #include "netsurf/download.h" #include "desktop/gui_internal.h" @@ -41,7 +42,7 @@ struct download_context { struct gui_window *parent; /**< Parent window */ lwc_string *mime_type; /**< MIME type of download */ - unsigned long total_length; /**< Length of data, in bytes */ + unsigned long long int total_length; /**< Length of data, in bytes */ char *filename; /**< Suggested filename */ struct gui_download_window *window; /**< GUI download window */ @@ -93,7 +94,7 @@ static nserror download_context_process_headers(download_context *ctx) { const char *http_header; http_content_type *content_type; - unsigned long length; + unsigned long long int length; nserror error; /* Retrieve and parse Content-Type */ @@ -107,10 +108,11 @@ static nserror download_context_process_headers(download_context *ctx) /* Retrieve and parse Content-Length */ http_header = llcache_handle_get_header(ctx->llcache, "Content-Length"); - if (http_header == NULL) + if (http_header == NULL) { length = 0; - else - length = strtoul(http_header, NULL, 10); + } else { + length = strtoull(http_header, NULL, 10); + } /* Retrieve and parse Content-Disposition */ http_header = llcache_handle_get_header(ctx->llcache, @@ -180,6 +182,9 @@ static nserror download_callback(llcache_handle *handle, nserror error = NSERROR_OK; switch (event->type) { + case LLCACHE_EVENT_GOT_CERTS: + /* Nominally not interested in these */ + break; case LLCACHE_EVENT_HAD_HEADERS: error = download_context_process_headers(ctx); if (error != NSERROR_OK) { @@ -223,7 +228,7 @@ static nserror download_callback(llcache_handle *handle, case LLCACHE_EVENT_ERROR: if (ctx->window != NULL) - guit->download->error(ctx->window, event->data.msg); + guit->download->error(ctx->window, event->data.error.msg); else download_context_destroy(ctx); @@ -295,7 +300,8 @@ const char *download_context_get_mime_type(const download_context *ctx) } /* See download.h for documentation */ -unsigned long download_context_get_total_length(const download_context *ctx) +unsigned long long int +download_context_get_total_length(const download_context *ctx) { return ctx->total_length; } diff --git a/desktop/download.h b/desktop/download.h index dc2befd90..b704c4c7d 100644 --- a/desktop/download.h +++ b/desktop/download.h @@ -91,7 +91,7 @@ const char *download_context_get_mime_type(const download_context *ctx); * \param ctx Context to retrieve byte length from * \return Total length, in bytes, or 0 if unknown */ -unsigned long download_context_get_total_length(const download_context *ctx); +unsigned long long int download_context_get_total_length(const download_context *ctx); /** * Retrieve the filename for a download diff --git a/desktop/font_haru.c b/desktop/font_haru.c index f92d89197..4ee9824f0 100644 --- a/desktop/font_haru.c +++ b/desktop/font_haru.c @@ -352,7 +352,7 @@ bool haru_nsfont_apply_style(const plot_font_style_t *fstyle, if (size <= 0) return true; - size /= FONT_SIZE_SCALE; + size /= PLOT_STYLE_SCALE; if (size > HPDF_MAX_FONTSIZE) size = HPDF_MAX_FONTSIZE; diff --git a/desktop/frames.c b/desktop/frames.c index e22287630..85f18793e 100644 --- a/desktop/frames.c +++ b/desktop/frames.c @@ -33,8 +33,9 @@ #include "utils/utils.h" #include "netsurf/content.h" #include "content/hlcache.h" -#include "render/html.h" -#include "render/box.h" +#include "html/html.h" +#include "html/box.h" +#include "html/box_inspect.h" #include "desktop/browser_private.h" #include "desktop/frames.h" @@ -68,7 +69,7 @@ void browser_window_scroll_callback(void *client_data, rect.x1 = rect.x0 + bw->width; rect.y1 = rect.y0 + bw->height; - browser_window_update_box(bw, &rect); + browser_window_invalidate_rect(bw, &rect); } break; case SCROLLBAR_MSG_SCROLL_START: @@ -180,20 +181,35 @@ void browser_window_handle_scrollbars(struct browser_window *bw) scrollbar_make_pair(bw->scroll_x, bw->scroll_y); } +/* exported function documented in desktop/frames.h */ +nserror browser_window_invalidate_iframe(struct browser_window *bw) +{ + html_redraw_a_box(bw->parent->current_content, bw->box); + return NSERROR_OK; +} /* exported function documented in desktop/frames.h */ -nserror browser_window_create_iframes(struct browser_window *bw, - struct content_html_iframe *iframe) +nserror browser_window_create_iframes(struct browser_window *bw) { + nserror ret = NSERROR_OK; struct browser_window *window; struct content_html_iframe *cur; struct rect rect; int iframes = 0; int index; - nserror ret = NSERROR_OK; + struct content_html_iframe *iframe; + + bw->iframe_count = 0; + + /* only html contents can have iframes */ + if (content_get_type(bw->current_content) != CONTENT_HTML) { + return NSERROR_OK; + } + /* obtain the iframes for this content */ + iframe = html_get_iframe(bw->current_content); if (iframe == NULL) { - return NSERROR_BAD_PARAMETER; + return NSERROR_OK; } /* Count iframe list and allocate enough space within the @@ -271,12 +287,7 @@ nserror browser_window_create_iframes(struct browser_window *bw, } -/** - * Recalculate iframe positions following a resize. - * - * \param bw The browser window to reposition iframes for - */ - +/* exported function documented in desktop/frames.h */ void browser_window_recalculate_iframes(struct browser_window *bw) { struct browser_window *window; @@ -292,123 +303,23 @@ void browser_window_recalculate_iframes(struct browser_window *bw) } -/* exported interface documented in desktop/frames.h */ -nserror browser_window_create_frameset(struct browser_window *bw, - struct content_html_frames *frameset) +/* exported function documented in desktop/frames.h */ +nserror browser_window_destroy_iframes(struct browser_window *bw) { - int row, col, index; - struct content_html_frames *frame; - struct browser_window *window; - hlcache_handle *parent; - - assert(bw && frameset); - - /* 1. Create children */ - assert(bw->children == NULL); - assert(frameset->cols + frameset->rows != 0); - - bw->children = calloc((frameset->cols * frameset->rows), sizeof(*bw)); - if (!bw->children) { - return NSERROR_NOMEM; - } - - bw->cols = frameset->cols; - bw->rows = frameset->rows; - for (row = 0; row < bw->rows; row++) { - for (col = 0; col < bw->cols; col++) { - index = (row * bw->cols) + col; - frame = &frameset->children[index]; - window = &bw->children[index]; - - /* Initialise common parts */ - browser_window_initialise_common(BW_CREATE_NONE, - window, NULL); + int i; - /* window characteristics */ - if (frame->children) - window->browser_window_type = - BROWSER_WINDOW_FRAMESET; - else - window->browser_window_type = - BROWSER_WINDOW_FRAME; - window->scrolling = frame->scrolling; - window->border = frame->border; - window->border_colour = frame->border_colour; - window->no_resize = frame->no_resize; - window->frame_width = frame->width; - window->frame_height = frame->height; - window->margin_width = frame->margin_width; - window->margin_height = frame->margin_height; - if (frame->name) { - window->name = strdup(frame->name); - if (!window->name) { - free(bw->children); - bw->children = NULL; - return NSERROR_NOMEM; - } + if (bw->iframes != NULL) { + for (i = 0; i < bw->iframe_count; i++) { + if (bw->iframes[i].box != NULL) { + bw->iframes[i].box->iframe = NULL; + bw->iframes[i].box = NULL; } - - window->scale = bw->scale; - - /* linking */ - window->parent = bw; - - if (window->name) - NSLOG(netsurf, INFO, "Created frame '%s'", - window->name); - else - NSLOG(netsurf, INFO, - "Created frame (unnamed)"); - } - } - - /* 2. Calculate dimensions */ - browser_window_update_extent(bw); - browser_window_recalculate_frameset(bw); - - /* 3. Recurse for grandchildren */ - for (row = 0; row < bw->rows; row++) { - for (col = 0; col < bw->cols; col++) { - index = (row * bw->cols) + col; - frame = &frameset->children[index]; - window = &bw->children[index]; - - if (frame->children) - browser_window_create_frameset(window, frame); + browser_window_destroy_internal(&bw->iframes[i]); } + free(bw->iframes); + bw->iframes = NULL; + bw->iframe_count = 0; } - - /* Use the URL of the first ancestor window containing html content - * as the referer */ - for (window = bw; window->parent; window = window->parent) { - if (window->current_content && - content_get_type(window->current_content) == - CONTENT_HTML) - break; - } - - parent = window->current_content; - - /* 4. Launch content */ - for (row = 0; row < bw->rows; row++) { - for (col = 0; col < bw->cols; col++) { - index = (row * bw->cols) + col; - frame = &frameset->children[index]; - window = &bw->children[index]; - - if (frame->url) { - browser_window_navigate(window, - frame->url, - hlcache_handle_get_url(parent), - BW_NAVIGATE_HISTORY | - BW_NAVIGATE_UNVERIFIABLE, - NULL, - NULL, - parent); - } - } - } - return NSERROR_OK; } @@ -416,10 +327,9 @@ nserror browser_window_create_frameset(struct browser_window *bw, /** * Recalculate frameset positions following a resize. * - * \param bw The browser window to reposition framesets for + * \param bw The browser window to reposition framesets for */ - -void browser_window_recalculate_frameset(struct browser_window *bw) +static void browser_window_recalculate_frameset_internal(struct browser_window *bw) { int widths[bw->cols][bw->rows]; int heights[bw->cols][bw->rows]; @@ -436,7 +346,9 @@ void browser_window_recalculate_frameset(struct browser_window *bw) /* window dimensions */ if (!bw->parent) { - browser_window_get_dimensions(bw, &bw_width, &bw_height, true); + browser_window_get_dimensions(bw, &bw_width, &bw_height); + bw_width /= bw->scale; + bw_height /= bw->scale; bw->x = 0; bw->y = 0; bw->width = bw_width; @@ -643,9 +555,177 @@ void browser_window_recalculate_frameset(struct browser_window *bw) x += widths[col][row]; if (window->children) - browser_window_recalculate_frameset(window); + browser_window_recalculate_frameset_internal(window); + } + } +} + + +/** + * Create and open a frameset for a browser window. + * + * \param[in,out] bw The browser window to create the frameset for + * \param[in] frameset The frameset to create + * \return NSERROR_OK or error code on faliure + */ +static nserror +browser_window_create_frameset_internal(struct browser_window *bw, + struct content_html_frames *frameset) +{ + int row, col, index; + struct content_html_frames *frame; + struct browser_window *window; + hlcache_handle *parent; + + assert(bw && frameset); + + /* 1. Create children */ + assert(bw->children == NULL); + assert(frameset->cols + frameset->rows != 0); + + bw->children = calloc((frameset->cols * frameset->rows), sizeof(*bw)); + if (!bw->children) { + return NSERROR_NOMEM; + } + + bw->cols = frameset->cols; + bw->rows = frameset->rows; + for (row = 0; row < bw->rows; row++) { + for (col = 0; col < bw->cols; col++) { + index = (row * bw->cols) + col; + frame = &frameset->children[index]; + window = &bw->children[index]; + + /* Initialise common parts */ + browser_window_initialise_common(BW_CREATE_NONE, + window, NULL); + + /* window characteristics */ + if (frame->children) + window->browser_window_type = + BROWSER_WINDOW_FRAMESET; + else + window->browser_window_type = + BROWSER_WINDOW_FRAME; + window->scrolling = frame->scrolling; + window->border = frame->border; + window->border_colour = frame->border_colour; + window->no_resize = frame->no_resize; + window->frame_width = frame->width; + window->frame_height = frame->height; + window->margin_width = frame->margin_width; + window->margin_height = frame->margin_height; + if (frame->name) { + window->name = strdup(frame->name); + if (!window->name) { + free(bw->children); + bw->children = NULL; + return NSERROR_NOMEM; + } + } + + window->scale = bw->scale; + + /* linking */ + window->parent = bw; + + if (window->name) + NSLOG(netsurf, INFO, "Created frame '%s'", + window->name); + else + NSLOG(netsurf, INFO, + "Created frame (unnamed)"); } } + + /* 2. Calculate dimensions */ + browser_window_update_extent(bw); + browser_window_recalculate_frameset_internal(bw); + + /* 3. Recurse for grandchildren */ + for (row = 0; row < bw->rows; row++) { + for (col = 0; col < bw->cols; col++) { + index = (row * bw->cols) + col; + frame = &frameset->children[index]; + window = &bw->children[index]; + + if (frame->children) + browser_window_create_frameset_internal(window, frame); + } + } + + /* Use the URL of the first ancestor window containing html content + * as the referer */ + for (window = bw; window->parent; window = window->parent) { + if (window->current_content && + content_get_type(window->current_content) == + CONTENT_HTML) + break; + } + + parent = window->current_content; + + /* 4. Launch content */ + for (row = 0; row < bw->rows; row++) { + for (col = 0; col < bw->cols; col++) { + index = (row * bw->cols) + col; + frame = &frameset->children[index]; + window = &bw->children[index]; + + if (frame->url) { + browser_window_navigate(window, + frame->url, + hlcache_handle_get_url(parent), + BW_NAVIGATE_HISTORY | + BW_NAVIGATE_UNVERIFIABLE, + NULL, + NULL, + parent); + } + } + } + + return NSERROR_OK; +} + + +/* exported interface documented in desktop/frames.h */ +nserror browser_window_create_frameset(struct browser_window *bw) +{ + struct content_html_frames *frameset; + + if (content_get_type(bw->current_content) != CONTENT_HTML) { + return NSERROR_OK; + } + + frameset = html_get_frameset(bw->current_content); + if (frameset == NULL) { + return NSERROR_OK; + } + + return browser_window_create_frameset_internal(bw, frameset); +} + + + + +/** + * Recalculate frameset positions following a resize. + * + * \param bw The browser window to reposition framesets for + */ + +void browser_window_recalculate_frameset(struct browser_window *bw) +{ + if (content_get_type(bw->current_content) != CONTENT_HTML) { + return; + } + + if (html_get_frameset(bw->current_content) == NULL) { + return; + } + + browser_window_recalculate_frameset_internal(bw); } @@ -700,7 +780,7 @@ void browser_window_resize_frame(struct browser_window *bw, int x, int y) } if (change) { - browser_window_recalculate_frameset(parent); + browser_window_recalculate_frameset_internal(parent); } } diff --git a/desktop/frames.h b/desktop/frames.h index 063e2c558..dda31824b 100644 --- a/desktop/frames.h +++ b/desktop/frames.h @@ -16,12 +16,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** \file +/** + * \file * Frame and frameset creation and manipulation (interface). */ -#ifndef _NETSURF_DESKTOP_FRAMES_H_ -#define _NETSURF_DESKTOP_FRAMES_H_ +#ifndef NETSURF_DESKTOP_FRAMES_H_ +#define NETSURF_DESKTOP_FRAMES_H_ struct scrollbar_msg_data; struct content_html_iframe; @@ -30,23 +31,40 @@ struct content_html_frames; /** * Create and open iframes for a browser window. * - * \param bw The browser window to create iframes for. - * \param iframe The iframes to create from. + * \param bw The browser window to create iframes for. * \return NSERROR_OK or error code on faliure. */ -nserror browser_window_create_iframes(struct browser_window *bw, - struct content_html_iframe *iframe); +nserror browser_window_create_iframes(struct browser_window *bw); + +/** + * Recalculate iframe positions following a resize. + * + * \param bw The browser window to reposition iframes for + */ void browser_window_recalculate_iframes(struct browser_window *bw); /** + * Invalidate an iframe causing a redraw. + * + * \param bw The browser window to invalidate + */ +nserror browser_window_invalidate_iframe(struct browser_window *bw); + +/** + * Destroy iframes opened in browser_window_create_iframes() + * + * \param bw The browser window to destroy iframes for. + * \return NSERROR_OK + */ +nserror browser_window_destroy_iframes(struct browser_window *bw); + +/** * Create and open a frameset for a browser window. * * \param[in,out] bw The browser window to create the frameset for - * \param[in] frameset The frameset to create * \return NSERROR_OK or error code on faliure */ -nserror browser_window_create_frameset(struct browser_window *bw, - struct content_html_frames *frameset); +nserror browser_window_create_frameset(struct browser_window *bw); void browser_window_recalculate_frameset(struct browser_window *bw); bool browser_window_frame_resize_start(struct browser_window *bw, diff --git a/desktop/global_history.c b/desktop/global_history.c index ad39a3e41..b64648e67 100644 --- a/desktop/global_history.c +++ b/desktop/global_history.c @@ -266,9 +266,9 @@ static nserror global_history_create_treeview_field_data( const char *title = (data->title != NULL) ? data->title : messages_get("NoTitle"); char buffer[16]; - const char *last_visited; - char *last_visited2; - int len; + struct tm *lvtime; + char *last_visited = NULL; + size_t len = 0; e->data[GH_TITLE].field = gh_ctx.fields[GH_TITLE].field; e->data[GH_TITLE].value = strdup(title); @@ -279,16 +279,18 @@ static nserror global_history_create_treeview_field_data( e->data[GH_URL].value = nsurl_access(e->url); e->data[GH_URL].value_len = nsurl_length(e->url); - last_visited = ctime(&data->last_visit); - last_visited2 = strdup(last_visited); - if (last_visited2 != NULL) { - assert(last_visited2[24] == '\n'); - last_visited2[24] = '\0'; + if ((lvtime = localtime(&data->last_visit)) != NULL) { + const size_t lvsize = 256; + last_visited = malloc(lvsize); + if (last_visited != NULL) { + len = strftime(last_visited, lvsize, + "%a %b %e %H:%M:%S %Y", lvtime); + } } e->data[GH_LAST_VISIT].field = gh_ctx.fields[GH_LAST_VISIT].field; - e->data[GH_LAST_VISIT].value = last_visited2; - e->data[GH_LAST_VISIT].value_len = (last_visited2 != NULL) ? 24 : 0; + e->data[GH_LAST_VISIT].value = last_visited; + e->data[GH_LAST_VISIT].value_len = len; len = snprintf(buffer, 16, "%u", data->visits); if (len == 16) { @@ -721,8 +723,7 @@ struct treeview_callback_table gh_tree_cb_t = { /* Exported interface, documented in global_history.h */ -nserror global_history_init(struct core_window_callback_table *cw_t, - void *core_window_handle) +nserror global_history_init(void *core_window_handle) { nserror err; @@ -753,7 +754,7 @@ nserror global_history_init(struct core_window_callback_table *cw_t, /* Create the global history treeview */ err = treeview_create(&gh_ctx.tree, &gh_tree_cb_t, N_FIELDS, gh_ctx.fields, - cw_t, core_window_handle, + core_window_handle, TREEVIEW_NO_MOVES | TREEVIEW_DEL_EMPTY_DIRS | TREEVIEW_SEARCHABLE); if (err != NSERROR_OK) { diff --git a/desktop/global_history.h b/desktop/global_history.h index 213a014f3..13e1cf398 100644 --- a/desktop/global_history.h +++ b/desktop/global_history.h @@ -25,7 +25,6 @@ #include "utils/errors.h" #include "netsurf/mouse.h" -struct core_window_callback_table; struct redraw_context; struct nsurl; struct rect; @@ -38,12 +37,10 @@ struct rect; * * This must be called before any other global_history_* function. * - * \param cw_t Callback table for core_window containing the treeview. - * \param core_window_handle The core_window in which the treeview is shown. + * \param core_window_handle The core_window in which the global history is shown. * \return NSERROR_OK on success, appropriate error otherwise */ -nserror global_history_init(struct core_window_callback_table *cw_t, - void *core_window_handle); +nserror global_history_init(void *core_window_handle); /** * Finalise the global history. diff --git a/desktop/gui_factory.c b/desktop/gui_factory.c index ca9eff1da..27365254a 100644 --- a/desktop/gui_factory.c +++ b/desktop/gui_factory.c @@ -19,10 +19,13 @@ #include <stdlib.h> #include <stdint.h> #include <stdbool.h> +#include <string.h> +#include <unistd.h> #include "utils/config.h" #include "utils/errors.h" #include "utils/file.h" +#include "utils/inet.h" #include "netsurf/bitmap.h" #include "content/hlcache.h" #include "content/backing_store.h" @@ -34,6 +37,7 @@ #include "netsurf/fetch.h" #include "netsurf/misc.h" #include "netsurf/window.h" +#include "netsurf/core_window.h" #include "netsurf/search.h" #include "netsurf/clipboard.h" #include "netsurf/utf8.h" @@ -55,14 +59,6 @@ static nserror gui_default_window_set_url(struct gui_window *g, struct nsurl *ur return NSERROR_OK; } -static void gui_default_window_start_throbber(struct gui_window *g) -{ -} - -static void gui_default_window_stop_throbber(struct gui_window *g) -{ -} - static bool gui_default_window_drag_start(struct gui_window *g, gui_drag_type type, const struct rect *rect) @@ -82,17 +78,6 @@ static void gui_default_window_set_icon(struct gui_window *g, { } - -static void gui_default_window_new_content(struct gui_window *g) -{ -} - - -static bool gui_default_window_scroll_start(struct gui_window *g) -{ - return true; -} - static void gui_default_window_set_pointer(struct gui_window *g, gui_pointer_shape shape) { @@ -109,10 +94,6 @@ static void gui_default_window_place_caret(struct gui_window *g, { } -static void gui_default_window_remove_caret(struct gui_window *g) -{ -} - static void gui_default_window_create_form_select_menu(struct gui_window *g, struct form_control *control) { @@ -135,7 +116,13 @@ static void gui_default_window_drag_save_selection(struct gui_window *g, { } -static void gui_default_window_start_selection(struct gui_window *g) + +static void +gui_default_console_log(struct gui_window *gw, + browser_window_console_source src, + const char *msg, + size_t msglen, + browser_window_console_flags flags) { } @@ -167,7 +154,7 @@ static nserror verify_window_register(struct gui_window_table *gwt) if (gwt->get_dimensions == NULL) { return NSERROR_BAD_PARAMETER; } - if (gwt->update_extent == NULL) { + if (gwt->event == NULL) { return NSERROR_BAD_PARAMETER; } @@ -191,27 +178,12 @@ static nserror verify_window_register(struct gui_window_table *gwt) if (gwt->place_caret == NULL) { gwt->place_caret = gui_default_window_place_caret; } - if (gwt->remove_caret == NULL) { - gwt->remove_caret = gui_default_window_remove_caret; - } - if (gwt->start_throbber == NULL) { - gwt->start_throbber = gui_default_window_start_throbber; - } - if (gwt->stop_throbber == NULL) { - gwt->stop_throbber = gui_default_window_stop_throbber; - } if (gwt->drag_start == NULL) { gwt->drag_start = gui_default_window_drag_start; } if (gwt->save_link == NULL) { gwt->save_link = gui_default_window_save_link; } - if (gwt->new_content == NULL) { - gwt->new_content = gui_default_window_new_content; - } - if (gwt->scroll_start == NULL) { - gwt->scroll_start = gui_default_window_scroll_start; - } if (gwt->create_form_select_menu == NULL) { gwt->create_form_select_menu = gui_default_window_create_form_select_menu; @@ -225,14 +197,83 @@ static nserror verify_window_register(struct gui_window_table *gwt) if (gwt->drag_save_selection == NULL) { gwt->drag_save_selection = gui_default_window_drag_save_selection; } - if (gwt->start_selection == NULL) { - gwt->start_selection = gui_default_window_start_selection; + if (gwt->console_log == NULL) { + gwt->console_log = gui_default_console_log; } return NSERROR_OK; } +static nserror gui_default_corewindow_invalidate(struct core_window *cw, const struct rect *rect) +{ + return NSERROR_OK; +} + +static nserror gui_default_corewindow_set_extent(struct core_window *cw, int width, int height) +{ + return NSERROR_OK; +} + +static nserror gui_default_corewindow_set_scroll(struct core_window *cw, int x, int y) +{ + return NSERROR_OK; +} + +static nserror gui_default_corewindow_get_scroll(const struct core_window *cw, int *x, int *y) +{ + return NSERROR_OK; +} + +static nserror gui_default_corewindow_get_dimensions(const struct core_window *cw, int *width, int *height) +{ + return NSERROR_OK; +} + +static nserror gui_default_corewindow_dragstatus(struct core_window *cw, core_window_drag_status ds) +{ + return NSERROR_OK; +} + +static struct core_window_table default_corewindow_table = { + .invalidate = gui_default_corewindow_invalidate, + .set_extent = gui_default_corewindow_set_extent, + .set_scroll = gui_default_corewindow_set_scroll, + .get_scroll = gui_default_corewindow_get_scroll, + .get_dimensions = gui_default_corewindow_get_dimensions, + .drag_status = gui_default_corewindow_dragstatus, +}; + +/** verify corewindow window table is valid */ +static nserror verify_corewindow_register(struct core_window_table *cwt) +{ + /* check table is present */ + if (cwt == NULL) { + return NSERROR_BAD_PARAMETER; + } + + /* all enties are mandantory */ + if (cwt->invalidate == NULL) { + return NSERROR_BAD_PARAMETER; + } + if (cwt->set_extent == NULL) { + return NSERROR_BAD_PARAMETER; + } + if (cwt->set_scroll == NULL) { + return NSERROR_BAD_PARAMETER; + } + if (cwt->get_scroll == NULL) { + return NSERROR_BAD_PARAMETER; + } + if (cwt->get_dimensions == NULL) { + return NSERROR_BAD_PARAMETER; + } + if (cwt->drag_status == NULL) { + return NSERROR_BAD_PARAMETER; + } + + return NSERROR_OK; +} static struct gui_download_window * gui_default_download_create(download_context *ctx, struct gui_window *parent) @@ -503,6 +544,16 @@ static char *gui_default_mimetype(const char *path) return strdup(guit->fetch->filetype(path)); } +static int gui_default_socket_open(int domain, int type, int protocol) +{ + return (int) socket(domain, type, protocol); +} + +static int gui_default_socket_close(int fd) +{ + return (int) ns_close_socket(fd); +} + /** verify fetch table is valid */ static nserror verify_fetch_register(struct gui_fetch_table *gft) { @@ -529,6 +580,12 @@ static nserror verify_fetch_register(struct gui_fetch_table *gft) if (gft->mimetype == NULL) { gft->mimetype = gui_default_mimetype; } + if (gft->socket_open == NULL) { + gft->socket_open = gui_default_socket_open; + } + if (gft->socket_close == NULL) { + gft->socket_close = gui_default_socket_close; + } return NSERROR_OK; } @@ -565,7 +622,7 @@ static nserror verify_file_register(struct gui_file_table *gft) * verify bitmap table is valid * * \param gbt The bitmap table to verify. - * \return NSERROR_OK if teh table is valid else NSERROR_BAD_PARAMETER. + * \return NSERROR_OK if the table is valid else NSERROR_BAD_PARAMETER. */ static nserror verify_bitmap_register(struct gui_bitmap_table *gbt) { @@ -591,10 +648,6 @@ static nserror verify_bitmap_register(struct gui_bitmap_table *gbt) return NSERROR_BAD_PARAMETER; } - if (gbt->test_opaque == NULL) { - return NSERROR_BAD_PARAMETER; - } - if (gbt->get_buffer == NULL) { return NSERROR_BAD_PARAMETER; } @@ -611,14 +664,6 @@ static nserror verify_bitmap_register(struct gui_bitmap_table *gbt) return NSERROR_BAD_PARAMETER; } - if (gbt->get_bpp == NULL) { - return NSERROR_BAD_PARAMETER; - } - - if (gbt->save == NULL) { - return NSERROR_BAD_PARAMETER; - } - if (gbt->modified == NULL) { return NSERROR_BAD_PARAMETER; } @@ -670,21 +715,18 @@ static nserror gui_default_launch_url(struct nsurl *url) } -static nserror gui_default_cert_verify(nsurl *url, - const struct ssl_cert_info *certs, - unsigned long num, - nserror (*cb)(bool proceed, void *pw), - void *cbpw) +static nserror gui_default_401login_open( + nsurl *url, const char *realm, + const char *username, const char *password, + nserror (*cb)(nsurl *url, const char * realm, + const char *username, + const char *password, + void *pw), + void *cbpw) { return NSERROR_NOT_IMPLEMENTED; } -static void gui_default_401login_open(nsurl *url, const char *realm, - nserror (*cb)(bool proceed, void *pw), void *cbpw) -{ - cb(false, cbpw); -} - static void gui_default_pdf_password(char **owner_pass, char **user_pass, char *path) { @@ -692,6 +734,12 @@ gui_default_pdf_password(char **owner_pass, char **user_pass, char *path) save_pdf(path); } +static nserror +gui_default_present_cookies(const char *search_term) +{ + return NSERROR_NOT_IMPLEMENTED; +} + /** verify misc table is valid */ static nserror verify_misc_register(struct gui_misc_table *gmt) { @@ -704,9 +752,6 @@ static nserror verify_misc_register(struct gui_misc_table *gmt) if (gmt->schedule == NULL) { return NSERROR_BAD_PARAMETER; } - if (gmt->warning == NULL) { - return NSERROR_BAD_PARAMETER; - } /* fill in the optional entries with defaults */ if (gmt->quit == NULL) { @@ -715,15 +760,15 @@ static nserror verify_misc_register(struct gui_misc_table *gmt) if (gmt->launch_url == NULL) { gmt->launch_url = gui_default_launch_url; } - if (gmt->cert_verify == NULL) { - gmt->cert_verify = gui_default_cert_verify; - } if (gmt->login == NULL) { gmt->login = gui_default_401login_open; } if (gmt->pdf_password == NULL) { gmt->pdf_password = gui_default_pdf_password; } + if (gmt->present_cookies == NULL) { + gmt->present_cookies = gui_default_present_cookies; + } return NSERROR_OK; } @@ -777,6 +822,17 @@ nserror netsurf_register(struct netsurf_table *gt) /* optional tables */ + /* core window table */ + if (gt->corewindow == NULL) { + /* set default core window table */ + gt->corewindow = &default_corewindow_table; + } + err = verify_corewindow_register(gt->corewindow); + if (err != NSERROR_OK) { + return err; + } + + /* file table */ if (gt->file == NULL) { gt->file = default_file_table; diff --git a/desktop/gui_table.h b/desktop/gui_table.h index 0ac9f0417..2a7c648bb 100644 --- a/desktop/gui_table.h +++ b/desktop/gui_table.h @@ -66,6 +66,15 @@ struct netsurf_table { struct gui_window_table *window; /** + * Core window table. + * + * Provides all operations which affect core ui windows of a frontend. + * + * The table is optional and may be NULL which does nothing. + */ + struct core_window_table *corewindow; + + /** * Download table. * * operations table for the download windows. diff --git a/desktop/hotlist.c b/desktop/hotlist.c index 4bdd7c8cb..57c582d21 100644 --- a/desktop/hotlist.c +++ b/desktop/hotlist.c @@ -193,29 +193,32 @@ static nserror hotlist_create_treeview_field_visits_data( struct hotlist_entry *e, const struct url_data *data) { char buffer[16]; - const char *last_visited; - char *last_visited2; - int len; + char *last_visited = NULL; + size_t len = 0; /* Last visited */ if (data->visits != 0) { - last_visited = ctime(&data->last_visit); - last_visited2 = strdup(last_visited); - len = 24; + const size_t lvsize = 256; + struct tm *lvtime; + + if ((lvtime = localtime(&data->last_visit)) != NULL) { + last_visited = malloc(lvsize); + if (last_visited != NULL) { + len = strftime(last_visited, lvsize, + "%a %b %e %H:%M:%S %Y", + lvtime); + } + } } else { - last_visited2 = strdup("-"); + last_visited = strdup("-"); len = 1; } - if (last_visited2 == NULL) { + if (last_visited == NULL) { return NSERROR_NOMEM; - - } else if (len == 24) { - assert(last_visited2[24] == '\n'); - last_visited2[24] = '\0'; } e->data[HL_LAST_VISIT].field = hl_ctx.fields[HL_LAST_VISIT].field; - e->data[HL_LAST_VISIT].value = last_visited2; + e->data[HL_LAST_VISIT].value = last_visited; e->data[HL_LAST_VISIT].value_len = len; /* Visits */ @@ -890,7 +893,9 @@ static nserror hotlist_load(const char *path, bool *loaded) corestring_lwc_html); if (html == NULL) { dom_node_unref(document); - guit->misc->warning("TreeLoadError", "(<html> not found)"); + NSLOG(netsurf, WARNING, + "%s (<html> not found)", + messages_get("TreeLoadError")); return NSERROR_OK; } @@ -899,7 +904,9 @@ static nserror hotlist_load(const char *path, bool *loaded) if (body == NULL) { dom_node_unref(html); dom_node_unref(document); - guit->misc->warning("TreeLoadError", "(<html>...<body> not found)"); + NSLOG(netsurf, WARNING, + "%s (<html>...<body> not found)", + messages_get("TreeLoadError")); return NSERROR_OK; } @@ -909,8 +916,9 @@ static nserror hotlist_load(const char *path, bool *loaded) dom_node_unref(body); dom_node_unref(html); dom_node_unref(document); - guit->misc->warning("TreeLoadError", - "(<html>...<body>...<ul> not found.)"); + NSLOG(netsurf, WARNING, + "%s (<html>...<body>...<ul> not found.)", + messages_get("TreeLoadError")); return NSERROR_OK; } @@ -934,7 +942,9 @@ static nserror hotlist_load(const char *path, bool *loaded) dom_node_unref(document); if (err != NSERROR_OK) { - guit->misc->warning("TreeLoadError", "(Failed building tree.)"); + NSLOG(netsurf, WARNING, + "%s (Failed building tree.)", + messages_get("TreeLoadError")); return NSERROR_OK; } @@ -961,13 +971,13 @@ static nserror hotlist_generate(void) const char *url; const char *msg_key; } default_entries[] = { - { "http://www.netsurf-browser.org/", + { "https://www.netsurf-browser.org/", "HotlistHomepage" }, - { "http://www.netsurf-browser.org/downloads/", + { "https://www.netsurf-browser.org/downloads/", "HotlistDownloads" }, - { "http://www.netsurf-browser.org/documentation", + { "https://www.netsurf-browser.org/documentation", "HotlistDocumentation" }, - { "http://www.netsurf-browser.org/contact", + { "https://www.netsurf-browser.org/contact", "HotlistContact" } }; const int n_entries = sizeof(default_entries) / @@ -1314,7 +1324,7 @@ nserror hotlist_init( /* Create the hotlist treeview */ err = treeview_create(&hl_ctx.tree, &hl_tree_cb_t, - HL_N_FIELDS, hl_ctx.fields, NULL, NULL, + HL_N_FIELDS, hl_ctx.fields, NULL, TREEVIEW_SEARCHABLE); if (err != NSERROR_OK) { free(hl_ctx.save_path); @@ -1341,13 +1351,12 @@ nserror hotlist_init( /* Exported interface, documented in hotlist.h */ -nserror hotlist_manager_init(struct core_window_callback_table *cw_t, - void *core_window_handle) +nserror hotlist_manager_init(void *core_window_handle) { nserror err; /* Create the hotlist treeview */ - err = treeview_cw_attach(hl_ctx.tree, cw_t, core_window_handle); + err = treeview_cw_attach(hl_ctx.tree, core_window_handle); if (err != NSERROR_OK) { return err; } @@ -1396,6 +1405,9 @@ nserror hotlist_fini(void) /* Destroy the hotlist treeview */ err = treeview_destroy(hl_ctx.tree); + if (err != NSERROR_OK) { + NSLOG(netsurf, INFO, "Problem destroying the hotlist treeview."); + } hl_ctx.built = false; /* Free hotlist treeview entry fields */ @@ -1616,7 +1628,7 @@ nserror hotlist_add_entry(nsurl *url, const char *title, bool at_y, int y) enum treeview_relationship rel; if (url == NULL) { - err = nsurl_create("http://netsurf-browser.org/", &url); + err = nsurl_create("https://netsurf-browser.org/", &url); if (err != NSERROR_OK) { return err; } diff --git a/desktop/hotlist.h b/desktop/hotlist.h index 571f4de97..aa9a57137 100644 --- a/desktop/hotlist.h +++ b/desktop/hotlist.h @@ -25,7 +25,6 @@ #include "utils/errors.h" #include "netsurf/mouse.h" -struct core_window_callback_table; struct redraw_context; struct nsurl; struct rect; @@ -47,9 +46,7 @@ struct rect; * \param save_path The path to save hotlist to, or NULL for read-only mode. * \return NSERROR_OK on success, appropriate error otherwise */ -nserror hotlist_init( - const char *load_path, - const char *save_path); +nserror hotlist_init(const char *load_path, const char *save_path); /** * Initialise the hotlist manager. @@ -58,12 +55,10 @@ nserror hotlist_init( * * The provided core window handle must be valid until hotlist_fini is called. * - * \param cw_t Callback table for core_window containing the treeview - * \param core_window_handle The handle in which the treeview is shown + * \param core_window_handle The handle in which the hotlist is shown * \return NSERROR_OK on success, appropriate error otherwise */ -nserror hotlist_manager_init(struct core_window_callback_table *cw_t, - void *core_window_handle); +nserror hotlist_manager_init(void *core_window_handle); /** diff --git a/desktop/knockout.c b/desktop/knockout.c index 6dbf4ebcf..127a48cdb 100644 --- a/desktop/knockout.c +++ b/desktop/knockout.c @@ -671,7 +671,6 @@ knockout_plot_polygon(const struct redraw_context *ctx, * \param pstyle Style controlling the path plot. * \param p elements of path * \param n nunber of elements on path - * \param width The width of the path * \param transform A transform to apply to the path. * \return NSERROR_OK on success else error code. */ @@ -680,14 +679,13 @@ knockout_plot_path(const struct redraw_context *ctx, const plot_style_t *pstyle, const float *p, unsigned int n, - float width, const float transform[6]) { nserror res; nserror ffres; ffres = knockout_plot_flush(ctx); - res = real_plot.path(ctx, pstyle, p, n, width, transform); + res = real_plot.path(ctx, pstyle, p, n, transform); /* return the first error */ if ((res != NSERROR_OK) && (ffres == NSERROR_OK)) { diff --git a/desktop/local_history.c b/desktop/local_history.c index 3219de90c..4f1299879 100644 --- a/desktop/local_history.c +++ b/desktop/local_history.c @@ -24,29 +24,33 @@ #include <stdlib.h> #include <string.h> -#include "utils/errors.h" #include "utils/nsurl.h" +#include "utils/errors.h" + #include "netsurf/types.h" #include "netsurf/layout.h" +#include "netsurf/browser_window.h" #include "netsurf/core_window.h" #include "netsurf/plotters.h" +#include "netsurf/keypress.h" + +#include "utils/nscolour.h" +#include "desktop/cw_helper.h" #include "desktop/gui_internal.h" #include "desktop/system_colour.h" #include "desktop/browser_private.h" #include "desktop/browser_history.h" +#include "desktop/local_history_private.h" #include "desktop/local_history.h" -#define WIDTH 100 -#define HEIGHT 86 - /** * local history viewer context */ struct local_history_session { struct browser_window *bw; - struct core_window_callback_table *cw_t; void *core_window_handle; + struct history_entry *cursor; }; @@ -55,7 +59,7 @@ struct local_history_session { */ static plot_style_t pstyle_line = { .stroke_type = PLOT_OP_TYPE_SOLID, - .stroke_width = 2, + .stroke_width = plot_style_int_to_fixed(2), }; @@ -72,7 +76,7 @@ static plot_style_t pstyle_bg = { */ static plot_style_t pstyle_rect = { .stroke_type = PLOT_OP_TYPE_SOLID, - .stroke_width = 1, + .stroke_width = plot_style_int_to_fixed(1), }; @@ -81,7 +85,16 @@ static plot_style_t pstyle_rect = { */ static plot_style_t pstyle_rect_sel = { .stroke_type = PLOT_OP_TYPE_SOLID, - .stroke_width = 3, + .stroke_width = plot_style_int_to_fixed(3), +}; + + +/** + * plot style for drawing rectangle round the cursor node + */ +static plot_style_t pstyle_rect_cursor = { + .stroke_type = PLOT_OP_TYPE_DASH, + .stroke_width = plot_style_int_to_fixed(3), }; @@ -90,7 +103,7 @@ static plot_style_t pstyle_rect_sel = { */ static plot_font_style_t pfstyle_node = { .family = PLOT_FONT_FAMILY_SANS_SERIF, - .size = 8 * FONT_SIZE_SCALE, + .size = 8 * PLOT_STYLE_SCALE, .weight = 400, .flags = FONTF_NONE, }; @@ -101,7 +114,7 @@ static plot_font_style_t pfstyle_node = { */ static plot_font_style_t pfstyle_node_sel = { .family = PLOT_FONT_FAMILY_SANS_SERIF, - .size = 8 * FONT_SIZE_SCALE, + .size = 8 * PLOT_STYLE_SCALE, .weight = 900, .flags = FONTF_NONE, }; @@ -120,6 +133,7 @@ static plot_font_style_t pfstyle_node_sel = { static nserror redraw_entry(struct history *history, struct history_entry *entry, + struct history_entry *cursor, struct rect *clip, int x, int y, const struct redraw_context *ctx) @@ -149,7 +163,8 @@ redraw_entry(struct history *history, entry->page.bitmap, entry->x + x, entry->y + y, - WIDTH, HEIGHT, + LOCAL_HISTORY_WIDTH, + LOCAL_HISTORY_HEIGHT, 0xffffff, 0); if (res != NSERROR_OK) { @@ -159,15 +174,27 @@ redraw_entry(struct history *history, rect.x0 = entry->x - 1 + x; rect.y0 = entry->y - 1 + y; - rect.x1 = entry->x + x + WIDTH; - rect.y1 = entry->y + y + HEIGHT; - res = ctx->plot->rectangle(ctx, pstyle, &rect); - if (res != NSERROR_OK) { - return res; + rect.x1 = entry->x + x + LOCAL_HISTORY_WIDTH; + rect.y1 = entry->y + y + LOCAL_HISTORY_HEIGHT; + + /* Border */ + if (entry != cursor) { + /* Not cursor position */ + res = ctx->plot->rectangle(ctx, pstyle, &rect); + if (res != NSERROR_OK) { + return res; + } + } else { + /* Cursor position */ + rect.x0 -= 1; + rect.y0 -= 1; + rect.x1 += 1; + rect.y1 += 1; + ctx->plot->rectangle(ctx, &pstyle_rect_cursor, &rect); } res = guit->layout->position(plot_style_font, entry->page.title, - strlen(entry->page.title), WIDTH, + strlen(entry->page.title), LOCAL_HISTORY_WIDTH, &char_offset, &actual_x); if (res != NSERROR_OK) { return res; @@ -176,7 +203,7 @@ redraw_entry(struct history *history, res = ctx->plot->text(ctx, pfstyle, entry->x + x, - entry->y + HEIGHT + 12 + y, + entry->y + LOCAL_HISTORY_HEIGHT + 12 + y, entry->page.title, char_offset); if (res != NSERROR_OK) { @@ -185,34 +212,34 @@ redraw_entry(struct history *history, /* for each child node draw a line and recurse redraw into it */ for (child = entry->forward; child; child = child->next) { - rect.x0 = entry->x + WIDTH + x; - rect.y0 = entry->y + HEIGHT / 2 + y; - rect.x1 = entry->x + WIDTH + tailsize + x; - rect.y1 = entry->y + HEIGHT / 2 + y; + rect.x0 = entry->x + LOCAL_HISTORY_WIDTH + x; + rect.y0 = entry->y + LOCAL_HISTORY_HEIGHT / 2 + y; + rect.x1 = entry->x + LOCAL_HISTORY_WIDTH + tailsize + x; + rect.y1 = entry->y + LOCAL_HISTORY_HEIGHT / 2 + y; res = ctx->plot->line(ctx, &pstyle_line, &rect); if (res != NSERROR_OK) { return res; } - rect.x0 = entry->x + WIDTH + tailsize + x; - rect.y0 = entry->y + HEIGHT / 2 + y; + rect.x0 = entry->x + LOCAL_HISTORY_WIDTH + tailsize + x; + rect.y0 = entry->y + LOCAL_HISTORY_HEIGHT / 2 + y; rect.x1 = child->x - tailsize + x; - rect.y1 = child->y + HEIGHT / 2 + y; + rect.y1 = child->y + LOCAL_HISTORY_HEIGHT / 2 + y; res = ctx->plot->line(ctx, &pstyle_line, &rect); if (res != NSERROR_OK) { return res; } rect.x0 = child->x - tailsize + x; - rect.y0 = child->y + HEIGHT / 2 + y; + rect.y0 = child->y + LOCAL_HISTORY_HEIGHT / 2 + y; rect.x1 = child->x + x; - rect.y1 = child->y + HEIGHT / 2 + y; + rect.y1 = child->y + LOCAL_HISTORY_HEIGHT / 2 + y; res = ctx->plot->line(ctx, &pstyle_line, &rect); if (res != NSERROR_OK) { return res; } - res = redraw_entry(history, child, clip, x, y, ctx); + res = redraw_entry(history, child, cursor, clip, x, y, ctx); if (res != NSERROR_OK) { return res; } @@ -241,9 +268,9 @@ find_entry_position(struct history_entry *entry, int x, int y) } if ((entry->x <= x) && - (x <= entry->x + WIDTH) && + (x <= entry->x + LOCAL_HISTORY_WIDTH) && (entry->y <= y) && - (y <= entry->y + HEIGHT)) { + (y <= entry->y + LOCAL_HISTORY_HEIGHT)) { return entry; } @@ -257,43 +284,51 @@ find_entry_position(struct history_entry *entry, int x, int y) return NULL; } +/* exported interface documented in desktop/local_history.h */ +nserror +local_history_scroll_to_cursor(struct local_history_session *session) +{ + rect cursor; + + if (session->cursor == NULL) { + return NSERROR_OK; + } + + cursor.x0 = session->cursor->x - LOCAL_HISTORY_RIGHT_MARGIN / 2; + cursor.y0 = session->cursor->y - LOCAL_HISTORY_BOTTOM_MARGIN / 2; + cursor.x1 = cursor.x0 + LOCAL_HISTORY_WIDTH + + LOCAL_HISTORY_RIGHT_MARGIN / 2; + cursor.y1 = cursor.y0 + LOCAL_HISTORY_HEIGHT + + LOCAL_HISTORY_BOTTOM_MARGIN / 2; + + return cw_helper_scroll_visible(session->core_window_handle, &cursor); +} /* exported interface documented in desktop/local_history.h */ nserror -local_history_init(struct core_window_callback_table *cw_t, - void *core_window_handle, +local_history_init(void *core_window_handle, struct browser_window *bw, struct local_history_session **session) { - nserror res; struct local_history_session *nses; - res = ns_system_colour_char("Window", &pstyle_bg.fill_colour); - if (res != NSERROR_OK) { - return res; - } - pfstyle_node.background = pstyle_bg.fill_colour; - pfstyle_node_sel.background = pstyle_bg.fill_colour; + pstyle_bg.fill_colour = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pstyle_line.stroke_colour = nscolours[NSCOLOUR_WIN_EVEN_BORDER]; - res = ns_system_colour_char("GrayText", &pstyle_line.stroke_colour); - if (res != NSERROR_OK) { - return res; - } pstyle_rect.stroke_colour = pstyle_line.stroke_colour; - pfstyle_node.foreground = pstyle_line.stroke_colour; + pstyle_rect_sel.stroke_colour = nscolours[NSCOLOUR_WIN_EVEN_BORDER]; + pstyle_rect_cursor.stroke_colour = nscolours[NSCOLOUR_SEL_BG]; - res = ns_system_colour_char("Highlight", &pstyle_rect_sel.stroke_colour); - if (res != NSERROR_OK) { - return res; - } - pfstyle_node_sel.foreground = pstyle_rect_sel.stroke_colour; + pfstyle_node.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG]; + pfstyle_node.background = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pfstyle_node_sel.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG]; + pfstyle_node_sel.background = nscolours[NSCOLOUR_WIN_EVEN_BG]; nses = calloc(1, sizeof(struct local_history_session)); if (nses == NULL) { return NSERROR_NOMEM; } - nses->cw_t = cw_t; nses->core_window_handle = core_window_handle; local_history_set(nses, bw); @@ -338,11 +373,13 @@ local_history_redraw(struct local_history_session *session, ctx->plot->clip(ctx, &r); ctx->plot->rectangle(ctx, &pstyle_bg, &r); - return redraw_entry(session->bw->history, - session->bw->history->start, - clip, - x, y, - ctx); + return redraw_entry( + session->bw->history, + session->bw->history->start, + session->cursor, + clip, + x, y, + ctx); } /* exported interface documented in desktop/local_history.h */ @@ -385,10 +422,120 @@ local_history_mouse_action(struct local_history_session *session, return NSERROR_OK; } +/** + * Determine the point on the parent line where this history line branches. + * + * If `branch_point` gets set then there is a guarantee that (a) `ent` is + * a transitive child (forward) of that point. and (b) `branch_point` has a + * parent. + * + * \param[in] ent The entry to work backward from + * \param[out] branch_point The entry to set to the branch point if one is found + */ +static void +_local_history_find_branch_point(struct history_entry *ent, + struct history_entry **branch_point) +{ + if (ent->back == NULL) { + /* We're at the root, nothing to do */ + return; + } + /* Start from our immediate parent */ + ent = ent->back; + while (ent->back != NULL) { + if (ent->back->forward != ent->back->forward_last) { + /* This point is a branch */ + *branch_point = ent; + break; + } + ent = ent->back; + } +} + /* exported interface documented in desktop/local_history.h */ bool local_history_keypress(struct local_history_session *session, uint32_t key) { + switch (key) { + case NS_KEY_NL: + case NS_KEY_CR: + /* pressed enter */ + if (session->cursor != session->bw->history->current) { + browser_window_history_go(session->bw, session->cursor, + false); + local_history_scroll_to_cursor(session); + guit->corewindow->invalidate(session->core_window_handle, NULL); + } + /* We have handled this keypress */ + return true; + case NS_KEY_LEFT: + /* Go to parent */ + if (session->cursor->back != NULL) { + session->cursor = session->cursor->back; + local_history_scroll_to_cursor(session); + guit->corewindow->invalidate(session->core_window_handle, NULL); + } + /* We have handled this keypress */ + return true; + case NS_KEY_RIGHT: + /* Go to preferred child if there is one */ + if (session->cursor->forward_pref != NULL) { + session->cursor = session->cursor->forward_pref; + local_history_scroll_to_cursor(session); + guit->corewindow->invalidate(session->core_window_handle, NULL); + } + /* We have handled this keypress */ + return true; + case NS_KEY_DOWN: + /* Go to next sibling down, if there is one */ + if (session->cursor->next != NULL) { + session->cursor = session->cursor->next; + } else { + struct history_entry *branch_point = NULL; + _local_history_find_branch_point( + session->cursor, + &branch_point); + if (branch_point != NULL) { + if (branch_point->next != NULL) { + branch_point = branch_point->next; + } + session->cursor = branch_point; + } + } + /* We have handled this keypress */ + local_history_scroll_to_cursor(session); + guit->corewindow->invalidate(session->core_window_handle, NULL); + return true; + case NS_KEY_UP: + /* Go to next sibling up, if there is one */ + if (session->cursor->back != NULL) { + struct history_entry *ent = session->cursor->back->forward; + while (ent != session->cursor && + ent->next != NULL && + ent->next != session->cursor) { + ent = ent->next; + } + if (session->cursor != ent) { + session->cursor = ent; + } else { + struct history_entry *branch_point = NULL; + _local_history_find_branch_point( + session->cursor, + &branch_point); + if (branch_point != NULL) { + struct history_entry *ent = branch_point->back->forward; + while (ent->next != NULL && ent->next != branch_point) { + ent = ent->next; + } + session->cursor = ent; + } + } + } + /* We have handled this keypress */ + local_history_scroll_to_cursor(session); + guit->corewindow->invalidate(session->core_window_handle, NULL); + return true; + } return false; } @@ -398,12 +545,16 @@ local_history_set(struct local_history_session *session, struct browser_window *bw) { session->bw = bw; + session->cursor = NULL; + if (bw != NULL) { assert(session->bw->history != NULL); + session->cursor = bw->history->current; - session->cw_t->update_size(session->core_window_handle, - session->bw->history->width, - session->bw->history->height); + guit->corewindow->set_extent(session->core_window_handle, + session->bw->history->width, + session->bw->history->height); + local_history_scroll_to_cursor(session); } return NSERROR_OK; diff --git a/desktop/local_history.h b/desktop/local_history.h index 7f85a633e..7f1986c11 100644 --- a/desktop/local_history.h +++ b/desktop/local_history.h @@ -25,7 +25,6 @@ #include "utils/errors.h" #include "netsurf/mouse.h" -struct core_window_callback_table; struct redraw_context; struct nsurl; struct rect; @@ -47,8 +46,7 @@ struct browser_window; * \param[out] session The created local history session context. * \return NSERROR_OK on success and session set, appropriate error code otherwise */ -nserror local_history_init(struct core_window_callback_table *cw_t, - void *core_window_handle, +nserror local_history_init(void *core_window_handle, struct browser_window *bw, struct local_history_session **session); @@ -140,5 +138,12 @@ nserror local_history_get_size(struct local_history_session *session, int *width */ nserror local_history_get_url(struct local_history_session *session, int x, int y, struct nsurl **url_out); +/** + * Scroll the local history window to ensure the current cursor is shown. + * + * \param session The local history session + */ +nserror local_history_scroll_to_cursor(struct local_history_session *session); + #endif diff --git a/desktop/local_history_private.h b/desktop/local_history_private.h new file mode 100644 index 000000000..fd25ab4d3 --- /dev/null +++ b/desktop/local_history_private.h @@ -0,0 +1,38 @@ +/* + * Copyright 2006 James Bursa <bursa@users.sourceforge.net> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * Interface to browser history private operations + */ + +#ifndef NETSURF_DESKTOP_BROWSER_HISTORY_PRIVATE_H +#define NETSURF_DESKTOP_BROWSER_HISTORY_PRIVATE_H + +#include "content/handlers/css/utils.h" + +#define LOCAL_HISTORY_WIDTH \ + (FIXTOINT(css_unit_css2device_px(INTTOFIX(116), nscss_screen_dpi))) +#define LOCAL_HISTORY_HEIGHT \ + (FIXTOINT(css_unit_css2device_px(INTTOFIX(100), nscss_screen_dpi))) +#define LOCAL_HISTORY_RIGHT_MARGIN \ + (FIXTOINT(css_unit_css2device_px(INTTOFIX( 50), nscss_screen_dpi))) +#define LOCAL_HISTORY_BOTTOM_MARGIN \ + (FIXTOINT(css_unit_css2device_px(INTTOFIX( 30), nscss_screen_dpi))) + +#endif diff --git a/desktop/netsurf.c b/desktop/netsurf.c index 8aa949a5a..bd785898f 100644 --- a/desktop/netsurf.c +++ b/desktop/netsurf.c @@ -24,14 +24,19 @@ #include <stdio.h> #include <stdlib.h> #include <libwapcaplet/libwapcaplet.h> +#include <dom/dom.h> #include "netsurf/inttypes.h" #include "utils/config.h" +#include "utils/errors.h" +#include "utils/nscolour.h" #include "utils/nsoption.h" #include "utils/corestrings.h" #include "utils/log.h" +#include "utils/string.h" #include "utils/utf8.h" #include "utils/messages.h" +#include "utils/useragent.h" #include "content/content_factory.h" #include "content/fetchers.h" #include "content/hlcache.h" @@ -41,16 +46,21 @@ #include "image/image.h" #include "image/image_cache.h" #include "javascript/js.h" -#include "render/html.h" -#include "render/textplain.h" +#include "html/html.h" +#include "text/textplain.h" #include "netsurf/browser_window.h" #include "desktop/system_colour.h" +#include "desktop/page-info.h" #include "desktop/searchweb.h" #include "netsurf/misc.h" #include "desktop/gui_internal.h" #include "netsurf/netsurf.h" + +/** \todo QUERY - Remove this import later */ +#include "desktop/browser_private.h" + /** speculative pre-conversion small image size * * Experimenting by visiting every page from default page in order and @@ -89,43 +99,10 @@ static void netsurf_lwc_iterator(lwc_string *str, void *pw) { - NSLOG(netsurf, INFO, "[%3u] %.*s", str->refcnt, + NSLOG(netsurf, WARNING, "[%3"PRIu32"] %.*s", str->refcnt, (int)lwc_string_length(str), lwc_string_data(str)); } -/** - * Dispatch a low-level cache query to the frontend - * - * \param query Query descriptor - * \param pw Private data - * \param cb Continuation callback - * \param cbpw Private data for continuation - * \return NSERROR_OK - */ -static nserror netsurf_llcache_query_handler(const llcache_query *query, - void *pw, llcache_query_response cb, void *cbpw) -{ - nserror res = NSERROR_OK; - - switch (query->type) { - case LLCACHE_QUERY_AUTH: - guit->misc->login(query->url, query->data.auth.realm, cb, cbpw); - break; - - case LLCACHE_QUERY_REDIRECT: - /** \todo Need redirect query dialog */ - /* For now, do nothing, as this query type isn't emitted yet */ - break; - - case LLCACHE_QUERY_SSL: - res = guit->misc->cert_verify(query->url, query->data.ssl.certs, - query->data.ssl.num, cb, cbpw); - break; - } - - return res; -} - /* exported interface documented in netsurf/netsurf.h */ nserror netsurf_init(const char *store_path) { @@ -133,7 +110,6 @@ nserror netsurf_init(const char *store_path) struct hlcache_parameters hlcache_parameters = { .bg_clean_time = HL_CACHE_CLEAN_TIME, .llcache = { - .cb = netsurf_llcache_query_handler, .minimum_lifetime = LLCACHE_STORE_MIN_LIFETIME, .minimum_bandwidth = LLCACHE_STORE_MIN_BANDWIDTH, .maximum_bandwidth = LLCACHE_STORE_MAX_BANDWIDTH, @@ -161,6 +137,10 @@ nserror netsurf_init(const char *store_path) if (ret != NSERROR_OK) return ret; + ret = nscolour_update(); + if (ret != NSERROR_OK) + return ret; + /* set up cache limits based on the memory cache size option */ hlcache_parameters.llcache.limit = nsoption_int(memory_cache_size); @@ -175,10 +155,10 @@ nserror netsurf_init(const char *store_path) hlcache_parameters.llcache.fetch_attempts = nsoption_uint(max_retried_fetches); /* image cache is 25% of total memory cache size */ - image_cache_parameters.limit = (hlcache_parameters.llcache.limit * 25) / 100; + image_cache_parameters.limit = hlcache_parameters.llcache.limit / 4; /* image cache hysteresis is 20% of the image cache size */ - image_cache_parameters.hysteresis = (image_cache_parameters.limit * 20) / 100; + image_cache_parameters.hysteresis = image_cache_parameters.limit / 5; /* account for image cache use from total */ hlcache_parameters.llcache.limit -= image_cache_parameters.limit; @@ -187,10 +167,13 @@ nserror netsurf_init(const char *store_path) hlcache_parameters.llcache.store.limit = nsoption_uint(disc_cache_size); /* set backing store hysterissi to 20% */ - hlcache_parameters.llcache.store.hysteresis = (hlcache_parameters.llcache.store.limit * 20) / 100;; + hlcache_parameters.llcache.store.hysteresis = hlcache_parameters.llcache.store.limit / 5; /* set the path to the backing store */ - hlcache_parameters.llcache.store.path = store_path; + hlcache_parameters.llcache.store.path = + nsoption_charp(disc_cache_path) ? + nsoption_charp(disc_cache_path) : + store_path; /* image handler bitmap cache */ ret = image_cache_init(&image_cache_parameters); @@ -233,6 +216,11 @@ nserror netsurf_init(const char *store_path) js_initialise(); + ret = page_info_init(); + if (ret != NSERROR_OK) { + return ret; + } + return NSERROR_OK; } @@ -247,7 +235,10 @@ void netsurf_exit(void) NSLOG(netsurf, INFO, "Closing GUI"); guit->misc->quit(); - + + NSLOG(netsurf, INFO, "Finalising page-info module"); + page_info_fini(); + NSLOG(netsurf, INFO, "Finalising JavaScript"); js_finalise(); @@ -259,6 +250,8 @@ void netsurf_exit(void) NSLOG(netsurf, INFO, "Closing fetches"); fetcher_quit(); + /* Now the fetchers are done, our user-agent string can go */ + free_user_agent_string(); /* dump any remaining cache entries */ image_cache_fini(); @@ -279,6 +272,9 @@ void netsurf_exit(void) messages_destroy(); corestrings_fini(); + if (dom_namespace_finalise() != DOM_NO_ERR) { + NSLOG(netsurf, WARNING, "Unable to finalise DOM namespace strings"); + } NSLOG(netsurf, INFO, "Remaining lwc strings:"); lwc_iterate_strings(netsurf_lwc_iterator, NULL); diff --git a/desktop/options.h b/desktop/options.h index 9b7064efa..70c6253f8 100644 --- a/desktop/options.h +++ b/desktop/options.h @@ -16,7 +16,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** \file +/** + * \file * Option available on all platforms * * Non-platform specific options can be added by editing this file @@ -27,8 +28,8 @@ * with different macro definitions so there is no guard */ -#ifndef _NETSURF_DESKTOP_OPTIONS_H_ -#define _NETSURF_DESKTOP_OPTIONS_H_ +#ifndef NETSURF_DESKTOP_OPTIONS_H_ +#define NETSURF_DESKTOP_OPTIONS_H_ #include "netsurf/types.h" @@ -91,6 +92,9 @@ NSOPTION_STRING(accept_charset, NULL) /** Preferred maximum size of memory cache / bytes. */ NSOPTION_INTEGER(memory_cache_size, 12 * 1024 * 1024) +/** Preferred location of disc cache, or NULL for system provided location */ +NSOPTION_STRING(disc_cache_path, NULL) + /** Preferred expiry size of disc cache / bytes. */ NSOPTION_UINT(disc_cache_size, 1024 * 1024 * 1024) @@ -104,9 +108,6 @@ NSOPTION_BOOL(block_advertisements, false) * http://www.w3.org/Submission/2011/SUBM-web-tracking-protection-20110224/#dnt-uas */ NSOPTION_BOOL(do_not_track, false) -/** Minimum GIF animation delay */ -NSOPTION_INTEGER(minimum_gif_delay, 10) - /** Whether to send the referer HTTP header */ NSOPTION_BOOL(send_referer, true) @@ -122,6 +123,9 @@ NSOPTION_BOOL(animate_images, true) /** Whether to execute javascript */ NSOPTION_BOOL(enable_javascript, false) +/** Whether to allow Author level CSS. */ +NSOPTION_BOOL(author_level_css, true) + /** Maximum time (in seconds) to wait for a script to run */ NSOPTION_INTEGER(script_timeout, 10) @@ -150,7 +154,7 @@ NSOPTION_STRING(homepage_url, NULL) NSOPTION_BOOL(search_url_bar, false) /** default web search provider */ -NSOPTION_INTEGER(search_provider, 0) +NSOPTION_STRING(search_web_provider, NULL) /** URL completion in url bar */ NSOPTION_BOOL(url_suggestion, true) @@ -167,12 +171,6 @@ NSOPTION_INTEGER(window_width, 0) /** default height of new windows */ NSOPTION_INTEGER(window_height, 0) -/** width of screen when above options were saved */ -NSOPTION_INTEGER(window_screen_width, 0) - -/** height of screen when above options were saved */ -NSOPTION_INTEGER(window_screen_height, 0) - /** default size of status bar vs. h scroll bar */ NSOPTION_INTEGER(toolbar_status_size, 6667) @@ -260,6 +258,9 @@ NSOPTION_BOOL(enable_PDF_compression, true) /** setting a password and encoding PDF documents */ NSOPTION_BOOL(enable_PDF_password, false) +/** whether to prefer dark mode (light on dark) */ +NSOPTION_BOOL(prefer_dark_mode, false) + /******** System colours ********/ NSOPTION_COLOUR(sys_colour_ActiveBorder, 0x00d3d3d3) NSOPTION_COLOUR(sys_colour_ActiveCaption, 0x00f1f1f1) @@ -291,6 +292,6 @@ NSOPTION_COLOUR(sys_colour_WindowFrame, 0x004e4e4e) NSOPTION_COLOUR(sys_colour_WindowText, 0x00000000) /** Filter for non-verbose logging */ -NSOPTION_STRING(log_filter, "level:WARNING") +NSOPTION_STRING(log_filter, NETSURF_BUILTIN_LOG_FILTER) /** Filter for verbose logging */ -NSOPTION_STRING(verbose_filter, "level:VERBOSE") +NSOPTION_STRING(verbose_filter, NETSURF_BUILTIN_VERBOSE_FILTER) diff --git a/desktop/page-info.c b/desktop/page-info.c new file mode 100644 index 000000000..c428a34be --- /dev/null +++ b/desktop/page-info.c @@ -0,0 +1,831 @@ +/* + * Copyright 2020 Michael Drake <tlsa@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * Pave info viewer window implementation + */ + +#include <stdlib.h> +#include <string.h> + +#include "css/utils.h" + +#include "utils/nsurl.h" +#include "utils/nscolour.h" + +#include "netsurf/mouse.h" +#include "netsurf/layout.h" +#include "netsurf/keypress.h" +#include "netsurf/plotters.h" +#include "netsurf/core_window.h" +#include "netsurf/browser_window.h" + +#include "desktop/knockout.h" +#include "desktop/page-info.h" +#include "desktop/gui_internal.h" +#include "desktop/system_colour.h" + +/** + * Plot style for heading font. + */ +static plot_font_style_t pi__heading[PAGE_STATE__COUNT] = { + [PAGE_STATE_UNKNOWN] = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 14 * PLOT_STYLE_SCALE, + .flags = FONTF_NONE, + .weight = 400, + }, + [PAGE_STATE_INTERNAL] = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 14 * PLOT_STYLE_SCALE, + .flags = FONTF_NONE, + .weight = 400, + }, + [PAGE_STATE_LOCAL] = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 14 * PLOT_STYLE_SCALE, + .flags = FONTF_NONE, + .weight = 400, + }, + [PAGE_STATE_INSECURE] = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 14 * PLOT_STYLE_SCALE, + .flags = FONTF_NONE, + .weight = 400, + }, + [PAGE_STATE_SECURE_OVERRIDE] = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 14 * PLOT_STYLE_SCALE, + .flags = FONTF_NONE, + .weight = 400, + }, + [PAGE_STATE_SECURE_ISSUES] = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 14 * PLOT_STYLE_SCALE, + .flags = FONTF_NONE, + .weight = 400, + }, + [PAGE_STATE_SECURE] = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 14 * PLOT_STYLE_SCALE, + .flags = FONTF_NONE, + .weight = 400, + }, +}; + +/** + * Plot style for domain font. + */ +static plot_font_style_t pi__domain = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 8 * PLOT_STYLE_SCALE, + .flags = FONTF_NONE, + .weight = 700, +}; + +/** + * Plot style for item font. + */ +static plot_font_style_t pi__item = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 11 * PLOT_STYLE_SCALE, + .flags = FONTF_NONE, + .weight = 400, +}; + +/** + * Plot style for item detail font. + */ +static plot_font_style_t pi__item_detail = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 11 * PLOT_STYLE_SCALE, + .flags = FONTF_NONE, + .weight = 400, +}; + +/** + * Plot style for window background. + */ +static plot_style_t pi__bg = { + .fill_type = PLOT_OP_TYPE_SOLID, +}; + +/** + * Plot style for hover background. + */ +static plot_style_t pi__hover = { + .fill_type = PLOT_OP_TYPE_SOLID, +}; + +/** + * An "text" type page info entry. + */ +struct page_info_text { + const char *text; + const plot_font_style_t *style; + int width; + int height; + int padding_bottom; +}; + +/** + * An "item" type page info entry. + */ +struct page_info_item { + struct page_info_text item; + struct page_info_text detail; + const plot_style_t *hover_bg; + int padding_bottom; + int padding_top; + bool hover; +}; + +/** + * List of page info window entries. + */ +enum pi_entry { + PI_ENTRY_HEADER, + PI_ENTRY_DOMAIN, + PI_ENTRY_CERT, + PI_ENTRY_COOKIES, + PI_ENTRY__COUNT, +}; + +/** + * An entry on a page info window. + */ +struct page_info_entry { + /** + * List of page info entry types. + */ + enum page_info_entry_type { + PAGE_INFO_ENTRY_TYPE_TEXT, + PAGE_INFO_ENTRY_TYPE_ITEM, + } type; + /** + * Type-specific page info entry data. + */ + union { + struct page_info_text text; + struct page_info_item item; + } u; +}; + +/** + * The default page info window data. + */ +struct page_info_entry pi__entries[PI_ENTRY__COUNT] = { + [PI_ENTRY_HEADER] = { + .type = PAGE_INFO_ENTRY_TYPE_TEXT, + }, + [PI_ENTRY_DOMAIN] = { + .type = PAGE_INFO_ENTRY_TYPE_TEXT, + .u = { + .text = { + .style = &pi__domain, + }, + }, + }, + [PI_ENTRY_CERT] = { + .type = PAGE_INFO_ENTRY_TYPE_ITEM, + .u = { + .item = { + .item = { + .style = &pi__item, + }, + .detail = { + .style = &pi__item_detail, + }, + .hover_bg = &pi__hover, + }, + }, + }, + [PI_ENTRY_COOKIES] = { + .type = PAGE_INFO_ENTRY_TYPE_ITEM, + .u = { + .item = { + .item = { + .style = &pi__item, + }, + .detail = { + .style = &pi__item_detail, + }, + .hover_bg = &pi__hover, + }, + }, + }, +}; + +/** + * The page info window structure. + */ +struct page_info { + struct core_window *cw_h; + + struct browser_window *bw; + lwc_string *domain; + enum nsurl_scheme_type scheme; + + browser_window_page_info_state state; + unsigned cookies; + + char cookie_text[64]; + struct page_info_entry entries[PI_ENTRY__COUNT]; + + int width; + int height; + + int window_padding; +}; + +/* Exported interface documented in desktop/page_info.h */ +nserror page_info_init(void) +{ + pi__bg.fill_colour = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pi__hover.fill_colour = nscolours[NSCOLOUR_WIN_EVEN_BG_HOVER]; + + pi__domain.background = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pi__domain.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG]; + + pi__item.background = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pi__item.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG]; + + pi__item_detail.background = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pi__item_detail.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_FADED]; + + pi__heading[PAGE_STATE_UNKNOWN].background = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pi__heading[PAGE_STATE_UNKNOWN].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_BAD]; + pi__heading[PAGE_STATE_INTERNAL].background = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pi__heading[PAGE_STATE_INTERNAL].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG]; + pi__heading[PAGE_STATE_LOCAL].background = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pi__heading[PAGE_STATE_LOCAL].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG]; + pi__heading[PAGE_STATE_INSECURE].background = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pi__heading[PAGE_STATE_INSECURE].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_BAD]; + pi__heading[PAGE_STATE_SECURE_OVERRIDE].background = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pi__heading[PAGE_STATE_SECURE_OVERRIDE].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_BAD]; + pi__heading[PAGE_STATE_SECURE_ISSUES].background = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pi__heading[PAGE_STATE_SECURE_ISSUES].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_BAD]; + pi__heading[PAGE_STATE_SECURE].background = nscolours[NSCOLOUR_WIN_EVEN_BG]; + pi__heading[PAGE_STATE_SECURE].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_GOOD]; + + return NSERROR_OK; +} + +/* Exported interface documented in desktop/page_info.h */ +nserror page_info_fini(void) +{ + return NSERROR_OK; +} + +/** + * Measure the text in the page_info window. + * + * \param[in] pi The page info window handle. + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +static nserror page_info__measure_text_entry( + struct page_info_text *pit) +{ + nserror err; + int height_px; + + err = guit->layout->width(pit->style, + pit->text, strlen(pit->text), + &pit->width); + if (err != NSERROR_OK) { + return err; + } + + /* \todo: This needs to be a helper in plot style or in nscss. */ + height_px = ((pit->style->size / PLOT_STYLE_SCALE) * + FIXTOINT(nscss_screen_dpi) + 36) / 72; + + pit->height = (height_px * 8 + 3) / 6; + + return NSERROR_OK; +} + +/** + * Measure the text in the page_info window. + * + * \param[in] pi The page info window handle. + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +static nserror page_info__measure_text( + struct page_info *pi) +{ + nserror err; + + for (unsigned i = 0; i < PI_ENTRY__COUNT; i++) { + struct page_info_entry *entry = pi->entries + i; + int padding; + + switch (entry->type) { + case PAGE_INFO_ENTRY_TYPE_TEXT: + err = page_info__measure_text_entry( + &entry->u.text); + if (err != NSERROR_OK) { + return err; + } + if (i == PI_ENTRY_DOMAIN) { + entry->u.text.padding_bottom = + entry->u.text.height * 3 / 2; + } + break; + + case PAGE_INFO_ENTRY_TYPE_ITEM: + err = page_info__measure_text_entry( + &entry->u.item.item); + if (err != NSERROR_OK) { + return err; + } + err = page_info__measure_text_entry( + &entry->u.item.detail); + if (err != NSERROR_OK) { + return err; + } + padding = entry->u.item.item.height / 4; + entry->u.item.padding_top = padding; + entry->u.item.padding_bottom = padding; + + break; + } + } + + pi->window_padding = pi->entries[PI_ENTRY_DOMAIN] + .u.item.item.height / 2; + + return NSERROR_OK; +} + +/** + * Set the text for the page_info window. + * + * \todo Use messages for internationalisation. + * + * \param[in] pi The page info window handle. + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +static nserror page_info__set_text( + struct page_info *pi) +{ + int printed; + static const char *header[PAGE_STATE__COUNT] = { + [PAGE_STATE_UNKNOWN] = "Provenance unknown", + [PAGE_STATE_INTERNAL] = "NetSurf data", + [PAGE_STATE_LOCAL] = "Local data", + [PAGE_STATE_INSECURE] = "Connection not secure", + [PAGE_STATE_SECURE_OVERRIDE] = "Connection not secure", + [PAGE_STATE_SECURE_ISSUES] = "Connection not secure", + [PAGE_STATE_SECURE] = "Connection is secure", + }; + static const char *certificate[PAGE_STATE__COUNT] = { + [PAGE_STATE_UNKNOWN] = "Missing", + [PAGE_STATE_INTERNAL] = "None", + [PAGE_STATE_LOCAL] = "None", + [PAGE_STATE_INSECURE] = "Not valid", + [PAGE_STATE_SECURE_OVERRIDE] = "Not valid", + [PAGE_STATE_SECURE_ISSUES] = "Not valid", + [PAGE_STATE_SECURE] = "Valid", + }; + + assert(pi != NULL); + assert(pi->state < PAGE_STATE__COUNT); + + pi->entries[PI_ENTRY_HEADER].u.text.style = &pi__heading[pi->state]; + pi->entries[PI_ENTRY_HEADER].u.text.text = header[pi->state]; + pi->entries[PI_ENTRY_DOMAIN].u.text.text = (pi->domain) ? + lwc_string_data(pi->domain) : "<No domain>"; + + pi->entries[PI_ENTRY_CERT].u.item.item.text = "Certificate: "; + pi->entries[PI_ENTRY_CERT].u.item.detail.text = certificate[pi->state]; + + printed = snprintf(pi->cookie_text, sizeof(pi->cookie_text), + "(%u in use)", pi->cookies); + if (printed < 0) { + return NSERROR_UNKNOWN; + + } else if ((unsigned) printed >= sizeof(pi->cookie_text)) { + return NSERROR_NOSPACE; + } + pi->entries[PI_ENTRY_COOKIES].u.item.item.text = "Cookies: "; + pi->entries[PI_ENTRY_COOKIES].u.item.detail.text = pi->cookie_text; + + return page_info__measure_text(pi); +} + +/** + * Create page info from a browser window. + * + * \param[in] pi The page info window handle. + * \param[in] bw Browser window to show page info for. + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +static nserror page_info__create_from_bw( + struct page_info *pi, + struct browser_window *bw) +{ + nsurl *url = browser_window_access_url(bw); + + pi->bw = bw; + pi->state = browser_window_get_page_info_state(bw); + pi->cookies = browser_window_get_cookie_count(bw); + pi->domain = nsurl_get_component(url, NSURL_HOST); + pi->scheme = nsurl_get_scheme_type(url); + + return page_info__set_text(pi); +} + +/** + * Check whether an entry is relevant. + * + * \param[in] entry The page info entry to consider. + * \param[in] scheme URL scheme that the page info is for. + * \return true if the entry should be hidden, otherwise false. + */ +static inline bool page_info__hide_entry( + enum pi_entry entry, + enum nsurl_scheme_type scheme) +{ + switch (entry) { + case PI_ENTRY_CERT: + if (scheme != NSURL_SCHEME_HTTPS) { + return true; + } + break; + case PI_ENTRY_COOKIES: + if (scheme != NSURL_SCHEME_HTTP && + scheme != NSURL_SCHEME_HTTPS) { + return true; + } + break; + default: + break; + } + + return false; +} + +/** + * Lay out the page info window. + * + * \param[in] pi The page info window handle. + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +static nserror page_info__layout( + struct page_info *pi) +{ + int cur_y = 0; + int max_x = 0; + + cur_y += pi->window_padding; + for (unsigned i = 0; i < PI_ENTRY__COUNT; i++) { + struct page_info_entry *entry = pi->entries + i; + + if (page_info__hide_entry(i, pi->scheme)) { + continue; + } + + switch (entry->type) { + case PAGE_INFO_ENTRY_TYPE_TEXT: + cur_y += entry->u.text.height; + if (max_x < entry->u.text.width) { + max_x = entry->u.text.width; + } + cur_y += entry->u.text.padding_bottom; + break; + + case PAGE_INFO_ENTRY_TYPE_ITEM: + { + int full_width = entry->u.item.item.width + + entry->u.item.detail.width; + cur_y += entry->u.item.padding_top; + cur_y += entry->u.item.item.height; + if (max_x < full_width) { + max_x = full_width; + } + cur_y += entry->u.item.padding_bottom; + } + break; + } + } + cur_y += pi->window_padding; + max_x += pi->window_padding * 2; + + pi->width = max_x; + pi->height = cur_y; + return guit->corewindow->set_extent(pi->cw_h, max_x, cur_y); +} + +/* Exported interface documented in desktop/page_info.h */ +nserror page_info_create(struct core_window *cw_h, + struct browser_window *bw, + struct page_info **pi_out) +{ + struct page_info *pi; + nserror err; + + pi = calloc(1, sizeof(*pi)); + if (pi == NULL) { + return NSERROR_NOMEM; + } + + pi->cw_h = cw_h; + + memcpy(pi->entries, pi__entries, sizeof(pi__entries)); + + err = page_info__create_from_bw(pi, bw); + if (err != NSERROR_OK) { + page_info_destroy(pi); + return err; + } + + err = page_info__layout(pi); + if (err != NSERROR_OK) { + page_info_destroy(pi); + return err; + } + + *pi_out = pi; + return NSERROR_OK; +} + +/* Exported interface documented in desktop/page_info.h */ +nserror page_info_destroy(struct page_info *pi) +{ + if (pi->domain != NULL) { + lwc_string_unref(pi->domain); + } + free(pi); + return NSERROR_OK; +} + +/* Exported interface documented in desktop/page_info.h */ +nserror page_info_set(struct page_info *pgi, struct browser_window *bw) +{ + nserror res; + + if (pgi->domain != NULL) { + lwc_string_unref(pgi->domain); + } + + res = page_info__create_from_bw(pgi, bw); + if (res == NSERROR_OK) { + res = page_info__layout(pgi); + } + + return res; +} + +/** + * Render a text entry. + * + * \param[in] pit The page info window handle. + * \param[in] x X-coordinate to plot at. + * \param[in] y Y-coordinate to plot at. + * \param[in] ctx Current redraw context. + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +static nserror page_info__redraw_text_entry( + const struct page_info_text *pit, + int x, + int y, + const struct redraw_context *ctx) +{ + int baseline = (pit->height * 3 + 2) / 4; + + ctx->plot->text(ctx, pit->style, x, y + baseline, + pit->text, strlen(pit->text)); + + return NSERROR_OK; +} + +/* Exported interface documented in desktop/page_info.h */ +nserror page_info_redraw( + const struct page_info *pi, + int x, + int y, + const struct rect *clip, + const struct redraw_context *ctx) +{ + struct redraw_context new_ctx = *ctx; + struct rect r = { + .x0 = clip->x0 + x, + .y0 = clip->y0 + y, + .x1 = clip->x1 + x, + .y1 = clip->y1 + y, + }; + int cur_y = y; + nserror err; + + /* Start knockout rendering if it's available for this plotter. */ + if (ctx->plot->option_knockout) { + bool res = knockout_plot_start(ctx, &new_ctx); + if (res == false) { + return NSERROR_UNKNOWN; + } + } + + /* Set up clip rectangle and draw background. */ + new_ctx.plot->clip(&new_ctx, &r); + new_ctx.plot->rectangle(&new_ctx, &pi__bg, &r); + + cur_y += pi->window_padding; + for (unsigned i = 0; i < PI_ENTRY__COUNT; i++) { + const struct page_info_entry *entry = pi->entries + i; + int cur_x = x + pi->window_padding; + + if (page_info__hide_entry(i, pi->scheme)) { + continue; + } + + switch (entry->type) { + case PAGE_INFO_ENTRY_TYPE_TEXT: + err = page_info__redraw_text_entry( + &entry->u.text, + cur_x, cur_y, + &new_ctx); + if (err != NSERROR_OK) { + goto cleanup; + } + cur_y += entry->u.text.height; + cur_y += entry->u.text.padding_bottom; + break; + + case PAGE_INFO_ENTRY_TYPE_ITEM: + if (entry->u.item.hover) { + r.y0 = cur_y; + r.y1 = cur_y + entry->u.item.padding_top + + entry->u.item.item.height + + entry->u.item.padding_bottom; + new_ctx.plot->rectangle(&new_ctx, + &pi__hover, &r); + } + cur_y += entry->u.item.padding_top; + err = page_info__redraw_text_entry( + &entry->u.item.item, + cur_x, cur_y, + &new_ctx); + if (err != NSERROR_OK) { + goto cleanup; + } + cur_x += entry->u.item.item.width; + err = page_info__redraw_text_entry( + &entry->u.item.detail, + cur_x, cur_y, + &new_ctx); + if (err != NSERROR_OK) { + goto cleanup; + } + cur_y += entry->u.item.item.height; + cur_y += entry->u.item.padding_bottom; + break; + } + } + +cleanup: + /* Rendering complete */ + if (ctx->plot->option_knockout) { + bool res = knockout_plot_end(ctx); + if (res == false) { + return NSERROR_UNKNOWN; + } + } + + return NSERROR_OK; +} + +/** + * Handle any clicks on an item. + * + * \param[in] pi The page info window handle. + * \param[in] mouse The current mouse state. + * \param[in] clicked The page info window entry to consider clicks on. + * \param[out] did_something Set to true if this click did something + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +static nserror page_info__handle_item_click( + struct page_info *pi, + enum browser_mouse_state mouse, + enum pi_entry clicked, + bool *did_something) +{ + nserror err; + + if (!(mouse & BROWSER_MOUSE_CLICK_1)) { + return NSERROR_OK; + } + + switch (clicked) { + case PI_ENTRY_CERT: + err = browser_window_show_certificates(pi->bw); + *did_something = true; + break; + case PI_ENTRY_COOKIES: + err = browser_window_show_cookies(pi->bw); + *did_something = true; + break; + default: + err = NSERROR_OK; + break; + } + + return err; +} + +/* Exported interface documented in desktop/page_info.h */ +nserror page_info_mouse_action( + struct page_info *pi, + enum browser_mouse_state mouse, + int x, + int y, + bool *did_something) +{ + int cur_y = 0; + nserror err; + + cur_y += pi->window_padding; + for (unsigned i = 0; i < PI_ENTRY__COUNT; i++) { + struct page_info_entry *entry = pi->entries + i; + bool hovering = false; + int height; + + if (page_info__hide_entry(i, pi->scheme)) { + continue; + } + + switch (entry->type) { + case PAGE_INFO_ENTRY_TYPE_TEXT: + cur_y += entry->u.text.height; + cur_y += entry->u.text.padding_bottom; + break; + + case PAGE_INFO_ENTRY_TYPE_ITEM: + height = entry->u.item.padding_top + + entry->u.item.item.height + + entry->u.item.padding_bottom; + + if (y >= cur_y && y < cur_y + height) { + hovering = true; + err = page_info__handle_item_click( + pi, mouse, i, did_something); + if (err != NSERROR_OK) { + return err; + } + } + if (entry->u.item.hover != hovering) { + int w, h; + struct rect r = { + .x0 = 0, + .y0 = cur_y, + .y1 = cur_y + height, + }; + guit->corewindow->get_dimensions(pi->cw_h, &w, &h); + r.x1 = (pi->width > w) ? pi->width : w; + + guit->corewindow->invalidate(pi->cw_h, &r); + } + entry->u.item.hover = hovering; + cur_y += height; + break; + } + } + + return NSERROR_OK; +} + +/* Exported interface documented in desktop/page_info.h */ +bool page_info_keypress( + struct page_info *pi, + int32_t key) +{ + return NSERROR_OK; +} + +/* Exported interface documented in desktop/page_info.h */ +nserror page_info_get_size( + struct page_info *pi, + int *width, + int *height) +{ + *width = pi->width; + *height = pi->height; + + return NSERROR_OK; +} diff --git a/desktop/page-info.h b/desktop/page-info.h new file mode 100644 index 000000000..1b05177a3 --- /dev/null +++ b/desktop/page-info.h @@ -0,0 +1,147 @@ +/* + * Copyright 2020 Michael Drake <tlsa@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * Pave info viewer window interface + */ + +#ifndef NETSURF_DESKTOP_PAGE_INFO_H +#define NETSURF_DESKTOP_PAGE_INFO_H + +#include <stdint.h> +#include <stdbool.h> + +#include "utils/errors.h" +#include "netsurf/mouse.h" + +struct rect; +struct nsurl; +struct page_info; +struct core_window; +struct browser_window; +struct redraw_context; + +/** + * Initialise the page_info module. + * + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +nserror page_info_init(void); + +/** + * Finalise the page_info module. + * + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +nserror page_info_fini(void); + +/** + * Create a page info corewindow. + * + * The page info window is opened for a particular browser window. + * It can be destroyed before the browser window is destroyed by calling + * \ref page_info_destroy. + * + * \param[in] cw_h Handle for the containing core_window. + * \param[in] bw Browser window to show page info for. + * \param[out] pi_out The created page info window handle. + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +nserror page_info_create(struct core_window *cw_h, + struct browser_window *bw, + struct page_info **pi_out); + +/** + * Destroy a page info corewindow. + * + * \param[in] pi The page info window handle. + */ +nserror page_info_destroy(struct page_info *pi); + +/** + * change the browser window the page information refers to + * + * \param[in] pgi The page info window context + * \param[in] bw The new browser window + * \return NSERROR_OK on sucess else error code. + */ +nserror page_info_set(struct page_info *pgi, struct browser_window *bw); + +/** + * Redraw the page info window. + * + * Causes the page info window to issue plot operations to redraw + * the specified area of the viewport. + * + * \param[in] pi The page info window handle. + * \param[in] x X coordinate to render page_info at. + * \param[in] y Y coordinate to render page_info at. + * \param[in] clip Current clip rectangle. + * \param[in] ctx Current redraw context. + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +nserror page_info_redraw( + const struct page_info *pi, + int x, + int y, + const struct rect *clip, + const struct redraw_context *ctx); + +/** + * Mouse action handling. + * + * \param[in] pi The page info window handle. + * \param[in] mouse The current mouse state + * \param[in] x The current mouse X coordinate + * \param[in] y The current mouse Y coordinate + * \param[out] did_something Set to true if this resulted in some action + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +nserror page_info_mouse_action( + struct page_info *pi, + enum browser_mouse_state mouse, + int x, + int y, + bool *did_something); + +/** + * Key press handling. + * + * \param[in] pi The page info window handle. + * \param[in] key The ucs4 character codepoint. + * \return true if the keypress is dealt with, false otherwise. + */ +bool page_info_keypress( + struct page_info *pi, + int32_t key); + +/** + * Get size of page info content area. + * + * \param[in] pi The page info window handle. + * \param[out] width On success, return the page info content width. + * \param[out] height On success, return the page info content height. + * \return NSERROR_OK on success, appropriate error code otherwise. + */ +nserror page_info_get_size( + struct page_info *pi, + int *width, + int *height); + +#endif diff --git a/desktop/plot_style.c b/desktop/plot_style.c index 1f0ac39cf..05954144a 100644 --- a/desktop/plot_style.c +++ b/desktop/plot_style.c @@ -47,7 +47,7 @@ plot_style_t *plot_style_fill_red = &plot_style_fill_red_static; static const plot_style_t plot_style_content_edge_static = { .stroke_type = PLOT_OP_TYPE_SOLID, .stroke_colour = 0x00ff0000, - .stroke_width = 1, + .stroke_width = plot_style_int_to_fixed(1), }; plot_style_t const * const plot_style_content_edge = &plot_style_content_edge_static; @@ -55,7 +55,7 @@ plot_style_t const * const plot_style_content_edge = static const plot_style_t plot_style_padding_edge_static = { .stroke_type = PLOT_OP_TYPE_SOLID, .stroke_colour = 0x000000ff, - .stroke_width = 1, + .stroke_width = plot_style_int_to_fixed(1), }; plot_style_t const * const plot_style_padding_edge = &plot_style_padding_edge_static; @@ -63,7 +63,7 @@ plot_style_t const * const plot_style_padding_edge = static const plot_style_t plot_style_margin_edge_static = { .stroke_type = PLOT_OP_TYPE_SOLID, .stroke_colour = 0x0000ffff, - .stroke_width = 1, + .stroke_width = plot_style_int_to_fixed(1), }; plot_style_t const * const plot_style_margin_edge = &plot_style_margin_edge_static; @@ -74,14 +74,14 @@ static const plot_style_t plot_style_broken_object_static = { .fill_colour = 0x008888ff, .stroke_type = PLOT_OP_TYPE_SOLID, .stroke_colour = 0x000000ff, - .stroke_width = 1, + .stroke_width = plot_style_int_to_fixed(1), }; plot_style_t const * const plot_style_broken_object = &plot_style_broken_object_static; static const plot_font_style_t plot_fstyle_broken_object_static = { .family = PLOT_FONT_FAMILY_SANS_SERIF, - .size = 16 * FONT_SIZE_SCALE, + .size = 16 * PLOT_STYLE_SCALE, .weight = 400, .flags = FONTF_NONE, .background = 0x8888ff, @@ -134,7 +134,7 @@ plot_style_t *plot_style_fill_wblobc = &plot_style_fill_wblobc_static; static plot_style_t plot_style_stroke_wblobc_static = { .stroke_type = PLOT_OP_TYPE_SOLID, .stroke_colour = WIDGET_BLOBC, - .stroke_width = 2, + .stroke_width = plot_style_int_to_fixed(2), }; plot_style_t *plot_style_stroke_wblobc = &plot_style_stroke_wblobc_static; @@ -156,7 +156,7 @@ plot_style_t *plot_style_stroke_lightwbasec = &plot_style_stroke_lightwbasec_sta /* Generic font style */ static const plot_font_style_t plot_style_font_static = { .family = PLOT_FONT_FAMILY_SANS_SERIF, - .size = 8 * FONT_SIZE_SCALE, + .size = 8 * PLOT_STYLE_SCALE, .weight = 400, .flags = FONTF_NONE, .background = 0xffffff, diff --git a/desktop/print.c b/desktop/print.c index 54cc5451a..e90e322ac 100644 --- a/desktop/print.c +++ b/desktop/print.c @@ -34,7 +34,7 @@ #include "netsurf/plotters.h" #include "content/hlcache.h" #include "css/utils.h" -#include "render/box.h" +#include "html/box.h" #include "desktop/print.h" #include "desktop/printer.h" @@ -257,6 +257,11 @@ struct print_settings *print_make_settings(print_configuration configuration, struct print_settings *settings; css_fixed length = 0; css_unit unit = CSS_UNIT_MM; + css_unit_ctx unit_len_ctx = { + .viewport_width = DEFAULT_PAGE_WIDTH, + .viewport_height = DEFAULT_PAGE_HEIGHT, + .root_style = NULL, + }; switch (configuration){ case PRINT_DEFAULT: @@ -272,17 +277,17 @@ struct print_settings *print_make_settings(print_configuration configuration, settings->scale = DEFAULT_EXPORT_SCALE; length = INTTOFIX(DEFAULT_MARGIN_LEFT_MM); - settings->margins[MARGINLEFT] = - nscss_len2px(length, unit, NULL); + settings->margins[MARGINLEFT] = css_unit_len2device_px( + NULL, &unit_len_ctx, length, unit); length = INTTOFIX(DEFAULT_MARGIN_RIGHT_MM); - settings->margins[MARGINRIGHT] = - nscss_len2px(length, unit, NULL); + settings->margins[MARGINRIGHT] = css_unit_len2device_px( + NULL, &unit_len_ctx, length, unit); length = INTTOFIX(DEFAULT_MARGIN_TOP_MM); - settings->margins[MARGINTOP] = - nscss_len2px(length, unit, NULL); + settings->margins[MARGINTOP] = css_unit_len2device_px( + NULL, &unit_len_ctx, length, unit); length = INTTOFIX(DEFAULT_MARGIN_BOTTOM_MM); - settings->margins[MARGINBOTTOM] = - nscss_len2px(length, unit, NULL); + settings->margins[MARGINBOTTOM] = css_unit_len2device_px( + NULL, &unit_len_ctx, length, unit); break; /* use settings from the Export options tab */ case PRINT_OPTIONS: @@ -298,17 +303,17 @@ struct print_settings *print_make_settings(print_configuration configuration, settings->scale = (float)nsoption_int(export_scale) / 100; length = INTTOFIX(nsoption_int(margin_left)); - settings->margins[MARGINLEFT] = - nscss_len2px(length, unit, NULL); + settings->margins[MARGINLEFT] = css_unit_len2device_px( + NULL, &unit_len_ctx, length, unit); length = INTTOFIX(nsoption_int(margin_right)); - settings->margins[MARGINRIGHT] = - nscss_len2px(length, unit, NULL); + settings->margins[MARGINRIGHT] = css_unit_len2device_px( + NULL, &unit_len_ctx, length, unit); length = INTTOFIX(nsoption_int(margin_top)); - settings->margins[MARGINTOP] = - nscss_len2px(length, unit, NULL); + settings->margins[MARGINTOP] = css_unit_len2device_px( + NULL, &unit_len_ctx, length, unit); length = INTTOFIX(nsoption_int(margin_bottom)); - settings->margins[MARGINBOTTOM] = - nscss_len2px(length, unit, NULL); + settings->margins[MARGINBOTTOM] = css_unit_len2device_px( + NULL, &unit_len_ctx, length, unit); break; default: return NULL; diff --git a/desktop/save_complete.c b/desktop/save_complete.c index 9a88ad180..e4fadd274 100644 --- a/desktop/save_complete.c +++ b/desktop/save_complete.c @@ -28,10 +28,10 @@ #include <string.h> #include <strings.h> #include <sys/types.h> -#include <regex.h> #include <dom/dom.h> #include "utils/config.h" +#include "utils/regex.h" #include "utils/corestrings.h" #include "utils/log.h" #include "utils/nsurl.h" @@ -43,14 +43,15 @@ #include "netsurf/content.h" #include "content/hlcache.h" #include "css/css.h" -#include "render/box.h" -#include "render/html.h" +#include "html/box.h" +#include "html/html_save.h" +#include "html/html.h" #include "netsurf/misc.h" #include "desktop/gui_internal.h" #include "desktop/save_complete.h" -regex_t save_complete_import_re; +static regex_t save_complete_import_re; /** An entry in save_complete_list. */ typedef struct save_complete_entry { @@ -74,9 +75,8 @@ typedef enum { } save_complete_event_type; -static bool save_complete_save_html(save_complete_ctx *ctx, struct hlcache_handle *c, - bool index); -static bool save_complete_save_imported_sheets(save_complete_ctx *ctx, +static nserror save_complete_save_html(save_complete_ctx *ctx, struct hlcache_handle *c, bool index); +static nserror save_complete_save_imported_sheets(save_complete_ctx *ctx, struct nscss_import *imports, uint32_t import_count); @@ -99,20 +99,22 @@ static void save_complete_ctx_finalise(save_complete_ctx *ctx) } } -static bool save_complete_ctx_add_content(save_complete_ctx *ctx, - struct hlcache_handle *content) +static nserror +save_complete_ctx_add_content(save_complete_ctx *ctx, + struct hlcache_handle *content) { save_complete_entry *entry; entry = malloc(sizeof (*entry)); - if (entry == NULL) - return false; + if (entry == NULL) { + return NSERROR_NOMEM; + } entry->content = content; entry->next = ctx->list; ctx->list = entry; - return true; + return NSERROR_OK; } /** @@ -139,21 +141,27 @@ save_complete_ctx_find_content(save_complete_ctx *ctx, const nsurl *url) } -static bool save_complete_ctx_has_content(save_complete_ctx *ctx, - struct hlcache_handle *content) +static bool +save_complete_ctx_has_content(save_complete_ctx *ctx, + struct hlcache_handle *content) { save_complete_entry *entry; - for (entry = ctx->list; entry != NULL; entry = entry->next) - if (entry->content == content) + for (entry = ctx->list; entry != NULL; entry = entry->next) { + if (hlcache_handle_get_content(entry->content) == + hlcache_handle_get_content(content)) return true; + } return false; } -static bool save_complete_save_buffer(save_complete_ctx *ctx, - const char *leafname, const char *data, size_t data_len, - lwc_string *mime_type) +static nserror +save_complete_save_buffer(save_complete_ctx *ctx, + const char *leafname, + const uint8_t *data, + size_t data_len, + lwc_string *mime_type) { nserror ret; FILE *fp; @@ -161,16 +169,14 @@ static bool save_complete_save_buffer(save_complete_ctx *ctx, ret = netsurf_mkpath(&fname, NULL, 2, ctx->path, leafname); if (ret != NSERROR_OK) { - guit->misc->warning(messages_get_errorcode(ret), 0); - return false; + return ret; } fp = fopen(fname, "wb"); if (fp == NULL) { free(fname); - NSLOG(netsurf, INFO, "fopen(): errno = %i", errno); - guit->misc->warning("SaveError", strerror(errno)); - return false; + NSLOG(netsurf, INFO, "fopen(): %s", strerror(errno)); + return NSERROR_SAVE_FAILED; } fwrite(data, sizeof(*data), data_len, fp); @@ -182,9 +188,38 @@ static bool save_complete_save_buffer(save_complete_ctx *ctx, } free(fname); - return true; + return NSERROR_OK; +} + + +/** + * perform a posix regexec on a string without a null terminator + */ +static int +snregexec(regex_t *preg, + const char *string, + size_t stringlen, + size_t nmatch, + regmatch_t pmatch[], + int eflags) +{ + char *strbuf; + int matches; + + strbuf = calloc(1, stringlen + 1); + if (strbuf == NULL) { + return -1; + } + memcpy(strbuf, string, stringlen); + + matches = regexec(preg, strbuf, nmatch, pmatch, eflags); + + free(strbuf); + + return matches; } + /** * Rewrite stylesheet \@import rules for save complete. * @@ -195,14 +230,14 @@ static bool save_complete_save_buffer(save_complete_ctx *ctx, * \param osize updated with the size of the result. * \return converted source, or NULL on out of memory. */ -static char * +static uint8_t * save_complete_rewrite_stylesheet_urls(save_complete_ctx *ctx, - const char *source, - unsigned long size, + const uint8_t *source, + size_t size, const nsurl *base, - unsigned long *osize) + size_t *osize) { - char *rewritten; + uint8_t *rewritten; unsigned long offset = 0; unsigned int imports = 0; nserror error; @@ -230,13 +265,19 @@ save_complete_rewrite_stylesheet_urls(save_complete_ctx *ctx, offset = 0; while (offset < size) { - const char *import_url = NULL; + const uint8_t *import_url = NULL; char *import_url_copy; int import_url_len = 0; nsurl *url = NULL; regmatch_t match[11]; - int m = regexec(&save_complete_import_re, source + offset, - 11, match, 0); + int m; + + m = snregexec(&save_complete_import_re, + (const char *)source + offset, + size - offset, + 11, + match, + 0); if (m) break; @@ -258,7 +299,8 @@ save_complete_rewrite_stylesheet_urls(save_complete_ctx *ctx, } assert(import_url != NULL); - import_url_copy = strndup(import_url, import_url_len); + import_url_copy = strndup((const char *)import_url, + import_url_len); if (import_url_copy == NULL) { free(rewritten); return NULL; @@ -314,44 +356,52 @@ save_complete_rewrite_stylesheet_urls(save_complete_ctx *ctx, return rewritten; } -static bool save_complete_save_stylesheet(save_complete_ctx *ctx, - hlcache_handle *css) +static nserror +save_complete_save_stylesheet(save_complete_ctx *ctx, hlcache_handle *css) { - const char *css_data; - unsigned long css_size; - char *source; - unsigned long source_len; + const uint8_t *css_data; + size_t css_size; + uint8_t *source; + size_t source_len; struct nscss_import *imports; uint32_t import_count; lwc_string *type; char filename[32]; - bool result; + nserror result; - if (save_complete_ctx_has_content(ctx, css)) - return true; + if (save_complete_ctx_find_content(ctx, + hlcache_handle_get_url(css)) != NULL) { + return NSERROR_OK; + } - if (save_complete_ctx_add_content(ctx, css) == false) { - guit->misc->warning("NoMemory", 0); - return false; + result = save_complete_ctx_add_content(ctx, css); + if (result != NSERROR_OK) { + return result; } imports = nscss_get_imports(css, &import_count); - if (save_complete_save_imported_sheets(ctx, - imports, import_count) == false) - return false; + result = save_complete_save_imported_sheets(ctx, + imports, + import_count); + if (result != NSERROR_OK) { + return result; + } css_data = content_get_source_data(css, &css_size); - source = save_complete_rewrite_stylesheet_urls(ctx, css_data, css_size, - hlcache_handle_get_url(css), &source_len); + source = save_complete_rewrite_stylesheet_urls( + ctx, + css_data, + css_size, + hlcache_handle_get_url(css), + &source_len); if (source == NULL) { - guit->misc->warning("NoMemory", 0); - return false; + return NSERROR_NOMEM; } type = content_get_mime_type(css); if (type == NULL) { free(source); - return false; + return NSERROR_NOMEM; } snprintf(filename, sizeof filename, "%p", css); @@ -365,70 +415,84 @@ static bool save_complete_save_stylesheet(save_complete_ctx *ctx, return result; } -static bool save_complete_save_imported_sheets(save_complete_ctx *ctx, - struct nscss_import *imports, uint32_t import_count) +static nserror +save_complete_save_imported_sheets(save_complete_ctx *ctx, + struct nscss_import *imports, + uint32_t import_count) { + nserror res = NSERROR_OK; uint32_t i; for (i = 0; i < import_count; i++) { /* treat a valid content as a stylesheet to save */ - if ((imports[i].c != NULL) && - (save_complete_save_stylesheet(ctx, imports[i].c) == false)) { - return false; + if (imports[i].c != NULL) { + res = save_complete_save_stylesheet(ctx, imports[i].c); + if (res != NSERROR_OK) { + return res; + } } } - return true; + return res; } -static bool save_complete_save_html_stylesheet(save_complete_ctx *ctx, - struct html_stylesheet *sheet) +static nserror +save_complete_save_html_stylesheet(save_complete_ctx *ctx, + struct html_stylesheet *sheet) { - if (sheet->sheet == NULL) - return true; + if (sheet->sheet == NULL) { + return NSERROR_OK; + } return save_complete_save_stylesheet(ctx, sheet->sheet); } -static bool save_complete_save_html_stylesheets(save_complete_ctx *ctx, - hlcache_handle *c) +static nserror +save_complete_save_html_stylesheets(save_complete_ctx *ctx, + hlcache_handle *c) { struct html_stylesheet *sheets; unsigned int i, count; + nserror res; sheets = html_get_stylesheets(c, &count); for (i = STYLESHEET_START; i != count; i++) { - if (save_complete_save_html_stylesheet(ctx, - &sheets[i]) == false) - return false; + res = save_complete_save_html_stylesheet(ctx, &sheets[i]); + if (res != NSERROR_OK) { + return res; + } } - return true; + return NSERROR_OK; } -static bool save_complete_save_html_object(save_complete_ctx *ctx, - hlcache_handle *obj) +static nserror +save_complete_save_html_object(save_complete_ctx *ctx, hlcache_handle *obj) { - const char *obj_data; - unsigned long obj_size; + const uint8_t *obj_data; + size_t obj_size; lwc_string *type; - bool result; + nserror result; char filename[32]; - if (content_get_type(obj) == CONTENT_NONE) - return true; + if (content_get_type(obj) == CONTENT_NONE) { + return NSERROR_OK; + } obj_data = content_get_source_data(obj, &obj_size); - if (obj_data == NULL) - return true; + if (obj_data == NULL) { + return NSERROR_OK; + } - if (save_complete_ctx_has_content(ctx, obj)) - return true; + if (save_complete_ctx_find_content(ctx, + hlcache_handle_get_url(obj)) != NULL) { + return NSERROR_OK; + } - if (save_complete_ctx_add_content(ctx, obj) == false) { - guit->misc->warning("NoMemory", 0); - return false; + result = save_complete_ctx_add_content(ctx, obj); + if (result != NSERROR_OK) { + return result; } if (content_get_type(obj) == CONTENT_HTML) { @@ -438,40 +502,46 @@ static bool save_complete_save_html_object(save_complete_ctx *ctx, snprintf(filename, sizeof filename, "%p", obj); type = content_get_mime_type(obj); - if (type == NULL) - return false; + if (type == NULL) { + return NSERROR_NOMEM; + } - result = save_complete_save_buffer(ctx, filename, - obj_data, obj_size, type); + result = save_complete_save_buffer(ctx, filename, obj_data, obj_size, type); lwc_string_unref(type); return result; } -static bool save_complete_save_html_objects(save_complete_ctx *ctx, - hlcache_handle *c) +static nserror +save_complete_save_html_objects(save_complete_ctx *ctx, + hlcache_handle *c) { struct content_html_object *object; unsigned int count; + nserror res; object = html_get_objects(c, &count); for (; object != NULL; object = object->next) { - if ((object->content != NULL) && (object->box != NULL)) { - if (save_complete_save_html_object(ctx, - object->content) == false) - return false; + if ((object->content != NULL) && + (object->box != NULL)) { + res = save_complete_save_html_object(ctx, object->content); + if (res != NSERROR_OK) { + return res; + } } } - return true; + return NSERROR_OK; } -static bool save_complete_libdom_treewalk(dom_node *root, - bool (*callback)(dom_node *node, - save_complete_event_type event_type, void *ctx), - void *ctx) +static bool +save_complete_libdom_treewalk(dom_node *root, + bool (*callback)(dom_node *node, + save_complete_event_type event_type, + void *ctx), + void *ctx) { dom_node *node; @@ -646,7 +716,7 @@ static bool save_complete_handle_attr_value(save_complete_ctx *ctx, * 4) background any (except those above) */ /* 1 */ - if (name_len == SLEN("data") && + if (name_len == SLEN("data") && strncasecmp(name_data, "data", name_len) == 0) { if (node_len == SLEN("object") && strncasecmp(node_data, @@ -661,13 +731,13 @@ static bool save_complete_handle_attr_value(save_complete_ctx *ctx, /* 2 */ else if (name_len == SLEN("href") && strncasecmp(name_data, "href", name_len) == 0) { - if ((node_len == SLEN("a") && + if ((node_len == SLEN("a") && strncasecmp(node_data, "a", node_len) == 0) || (node_len == SLEN("area") && - strncasecmp(node_data, "area", + strncasecmp(node_data, "area", node_len) == 0) || - (node_len == SLEN("link") && - strncasecmp(node_data, "link", + (node_len == SLEN("link") && + strncasecmp(node_data, "link", node_len) == 0)) { return save_complete_rewrite_url_value(ctx, value_data, value_len); @@ -675,7 +745,7 @@ static bool save_complete_handle_attr_value(save_complete_ctx *ctx, return save_complete_write_value(ctx, value_data, value_len); } - } + } /* 3 */ else if (name_len == SLEN("src") && strncasecmp(name_data, "src", name_len) == 0) { @@ -712,8 +782,10 @@ static bool save_complete_handle_attr_value(save_complete_ctx *ctx, } } -static bool save_complete_handle_attr(save_complete_ctx *ctx, - dom_string *node_name, dom_attr *attr) +static bool +save_complete_handle_attr(save_complete_ctx *ctx, + dom_string *node_name, + dom_attr *attr) { dom_string *name; const char *name_data; @@ -742,12 +814,13 @@ static bool save_complete_handle_attr(save_complete_ctx *ctx, if (value != NULL) { fputc('=', ctx->fp); - if (save_complete_handle_attr_value(ctx, node_name, + if (save_complete_handle_attr_value(ctx, node_name, name, value) == false) { dom_string_unref(value); dom_string_unref(name); return false; } + dom_string_unref(value); } dom_string_unref(name); @@ -755,8 +828,10 @@ static bool save_complete_handle_attr(save_complete_ctx *ctx, return true; } -static bool save_complete_handle_attrs(save_complete_ctx *ctx, - dom_string *node_name, dom_namednodemap *attrs) +static bool +save_complete_handle_attrs(save_complete_ctx *ctx, + dom_string *node_name, + dom_namednodemap *attrs) { uint32_t length, i; dom_exception error; @@ -786,8 +861,10 @@ static bool save_complete_handle_attrs(save_complete_ctx *ctx, return true; } -static bool save_complete_handle_element(save_complete_ctx *ctx, - dom_node *node, save_complete_event_type event_type) +static bool +save_complete_handle_element(save_complete_ctx *ctx, + dom_node *node, + save_complete_event_type event_type) { dom_string *name; dom_namednodemap *attrs; @@ -808,12 +885,12 @@ static bool save_complete_handle_element(save_complete_ctx *ctx, name_data = dom_string_data(name); name_len = dom_string_byte_length(name); - if (name_len == SLEN("base") && - strncasecmp(name_data, "base", name_len) == 0) { + if ((name_len == SLEN("base")) && + (strncasecmp(name_data, "base", name_len) == 0)) { /* Elide BASE elements from the output */ process = false; - } else if (name_len == SLEN("meta") && - strncasecmp(name_data, "meta", name_len) == 0) { + } else if ((name_len == SLEN("meta")) && + (strncasecmp(name_data, "meta", name_len) == 0)) { /* Don't emit close tags for META elements */ if (event_type == EVENT_LEAVE) { process = false; @@ -821,7 +898,8 @@ static bool save_complete_handle_element(save_complete_ctx *ctx, /* Elide meta charsets */ dom_string *value; error = dom_element_get_attribute(node, - corestring_dom_http_equiv, &value); + corestring_dom_http_equiv, + &value); if (error != DOM_NO_ERR) { dom_string_unref(name); return false; @@ -850,8 +928,8 @@ static bool save_complete_handle_element(save_complete_ctx *ctx, process = false; } } - } else if (event_type == EVENT_LEAVE && - ((name_len == SLEN("link") && + } else if (event_type == EVENT_LEAVE && + ((name_len == SLEN("link") && strncasecmp(name_data, "link", name_len) == 0))) { /* Don't emit close tags for void elements */ process = false; @@ -863,8 +941,9 @@ static bool save_complete_handle_element(save_complete_ctx *ctx, } fputc('<', ctx->fp); - if (event_type == EVENT_LEAVE) + if (event_type == EVENT_LEAVE) { fputc('/', ctx->fp); + } fwrite(name_data, sizeof(*name_data), name_len, ctx->fp); if (event_type == EVENT_ENTER) { @@ -897,13 +976,13 @@ static bool save_complete_handle_element(save_complete_ctx *ctx, } if (content != NULL) { - char *rewritten; - unsigned long len; + uint8_t *rewritten; + size_t len; /* Rewrite @import rules */ rewritten = save_complete_rewrite_stylesheet_urls( ctx, - dom_string_data(content), + (const uint8_t *)dom_string_data(content), dom_string_byte_length(content), ctx->base, &len); @@ -934,8 +1013,10 @@ static bool save_complete_handle_element(save_complete_ctx *ctx, return true; } -static bool save_complete_node_handler(dom_node *node, - save_complete_event_type event_type, void *ctxin) +static bool +save_complete_node_handler(dom_node *node, + save_complete_event_type event_type, + void *ctxin) { save_complete_ctx *ctx = ctxin; dom_node_type type; @@ -977,7 +1058,7 @@ static bool save_complete_node_handler(dom_node *node, if (ret != NSERROR_OK) return false; - fwrite(escaped, sizeof(*escaped), + fwrite(escaped, sizeof(*escaped), strlen(escaped), ctx->fp); free(escaped); @@ -1053,8 +1134,10 @@ static bool save_complete_node_handler(dom_node *node, return true; } -static bool save_complete_save_html_document(save_complete_ctx *ctx, - hlcache_handle *c, bool index) +static nserror +save_complete_save_html_document(save_complete_ctx *ctx, + hlcache_handle *c, + bool index) { nserror ret; FILE *fp; @@ -1071,16 +1154,14 @@ static bool save_complete_save_html_document(save_complete_ctx *ctx, ret = netsurf_mkpath(&fname, NULL, 2, ctx->path, filename); if (ret != NSERROR_OK) { - guit->misc->warning(messages_get_errorcode(ret), NULL); - return false; + return ret; } fp = fopen(fname, "wb"); if (fp == NULL) { free(fname); - NSLOG(netsurf, INFO, "fopen(): errno = %i", errno); - guit->misc->warning("SaveError", strerror(errno)); - return false; + NSLOG(netsurf, INFO, "fopen(): %s", strerror(errno)); + return NSERROR_SAVE_FAILED; } ctx->base = html_get_base_url(c); @@ -1089,26 +1170,27 @@ static bool save_complete_save_html_document(save_complete_ctx *ctx, doc = html_get_document(c); - if (save_complete_libdom_treewalk((dom_node *) doc, - save_complete_node_handler, ctx) == false) { + if (save_complete_libdom_treewalk((dom_node *)doc, + save_complete_node_handler, + ctx) == false) { free(fname); - guit->misc->warning("NoMemory", 0); fclose(fp); - return false; + return NSERROR_NOMEM; } fclose(fp); mime_type = content_get_mime_type(c); if (mime_type != NULL) { - if (ctx->set_type != NULL) + if (ctx->set_type != NULL) { ctx->set_type(fname, mime_type); + } lwc_string_unref(mime_type); } free(fname); - return true; + return NSERROR_OK; } /** @@ -1119,20 +1201,30 @@ static bool save_complete_save_html_document(save_complete_ctx *ctx, * \param index true to save as "index" * \return true on success, false on error and error reported */ -static bool save_complete_save_html(save_complete_ctx *ctx, hlcache_handle *c, - bool index) +static nserror +save_complete_save_html(save_complete_ctx *ctx, + hlcache_handle *c, + bool index) { - if (content_get_type(c) != CONTENT_HTML) - return false; + nserror res; - if (save_complete_ctx_has_content(ctx, c)) - return true; + if (content_get_type(c) != CONTENT_HTML) { + return NSERROR_INVALID; + } - if (save_complete_save_html_stylesheets(ctx, c) == false) - return false; + if (save_complete_ctx_has_content(ctx, c)) { + return NSERROR_OK; + } - if (save_complete_save_html_objects(ctx, c) == false) - return false; + res = save_complete_save_html_stylesheets(ctx, c); + if (res != NSERROR_OK) { + return res; + } + + res = save_complete_save_html_objects(ctx, c); + if (res != NSERROR_OK) { + return res; + } return save_complete_save_html_document(ctx, c, index); } @@ -1142,7 +1234,7 @@ static bool save_complete_save_html(save_complete_ctx *ctx, hlcache_handle *c, * Create the inventory file listing original URLs. */ -static bool save_complete_inventory(save_complete_ctx *ctx) +static nserror save_complete_inventory(save_complete_ctx *ctx) { nserror ret; FILE *fp; @@ -1151,26 +1243,26 @@ static bool save_complete_inventory(save_complete_ctx *ctx) ret = netsurf_mkpath(&fname, NULL, 2, ctx->path, "Inventory"); if (ret != NSERROR_OK) { - return false; + return ret; } fp = fopen(fname, "w"); free(fname); if (fp == NULL) { - NSLOG(netsurf, INFO, "fopen(): errno = %i", errno); - guit->misc->warning("SaveError", strerror(errno)); - return false; + NSLOG(netsurf, INFO, "fopen(): %s", strerror(errno)); + return NSERROR_SAVE_FAILED; } for (entry = ctx->list; entry != NULL; entry = entry->next) { - fprintf(fp, "%p %s\n", entry->content, - nsurl_access(hlcache_handle_get_url( - entry->content))); + fprintf(fp, "%p %s\n", + entry->content, + nsurl_access(hlcache_handle_get_url( + entry->content))); } fclose(fp); - return true; + return NSERROR_OK; } /** @@ -1227,17 +1319,26 @@ void save_complete_init(void) } /* Documented in save_complete.h */ -bool save_complete(hlcache_handle *c, const char *path, - save_complete_set_type_cb set_type) +nserror save_complete_finalise(void) +{ + regfree(&save_complete_import_re); + return NSERROR_OK; +} + +/* Documented in save_complete.h */ +nserror +save_complete(hlcache_handle *c, + const char *path, + save_complete_set_type_cb set_type) { - bool result; + nserror result; save_complete_ctx ctx; save_complete_ctx_initialise(&ctx, path, set_type); - + result = save_complete_save_html(&ctx, c, true); - if (result) { + if (result == NSERROR_OK) { result = save_complete_inventory(&ctx); } @@ -1245,4 +1346,3 @@ bool save_complete(hlcache_handle *c, const char *path, return result; } - diff --git a/desktop/save_complete.h b/desktop/save_complete.h index 3c389a56f..7df89f0b5 100644 --- a/desktop/save_complete.h +++ b/desktop/save_complete.h @@ -21,8 +21,8 @@ * Save HTML document with dependencies (interface). */ -#ifndef _NETSURF_DESKTOP_SAVE_COMPLETE_H_ -#define _NETSURF_DESKTOP_SAVE_COMPLETE_H_ +#ifndef NETSURF_DESKTOP_SAVE_COMPLETE_H_ +#define NETSURF_DESKTOP_SAVE_COMPLETE_H_ #include <stdbool.h> @@ -44,15 +44,21 @@ typedef void (*save_complete_set_type_cb)(const char *path, */ void save_complete_init(void); + +/** + * Finalise save complete module. + */ +nserror save_complete_finalise(void); + /** * Save an HTML page with all dependencies. * * \param c CONTENT_HTML to save * \param path Native path to directory to save in to (must exist) * \param set_type Callback to set type of a file, or NULL - * \return true on success, false on error and error reported + * \return NSERROR_OK on success else error code */ -bool save_complete(struct hlcache_handle *c, const char *path, +nserror save_complete(struct hlcache_handle *c, const char *path, save_complete_set_type_cb set_type); #endif diff --git a/desktop/save_pdf.c b/desktop/save_pdf.c index 83e3d4f31..889190089 100644 --- a/desktop/save_pdf.c +++ b/desktop/save_pdf.c @@ -213,10 +213,10 @@ bool pdf_plot_rectangle(int x0, int y0, int x1, int y1, const plot_style_t *psty } apply_clip_and_mode(false, - NS_TRANSPARENT, - pstyle->stroke_colour, - pstyle->stroke_width, - dash); + NS_TRANSPARENT, + pstyle->stroke_colour, + plot_style_int_to_fixed(pstyle->stroke_width), + dash); HPDF_Page_Rectangle(pdf_page, x0, page_height - y0, x1 - x0, -(y1 - y0)); HPDF_Page_Stroke(pdf_page); @@ -245,10 +245,10 @@ bool pdf_plot_line(int x0, int y0, int x1, int y1, const plot_style_t *pstyle) } apply_clip_and_mode(false, - NS_TRANSPARENT, - pstyle->stroke_colour, - pstyle->stroke_width, - dash); + NS_TRANSPARENT, + pstyle->stroke_colour, + plot_style_int_to_fixed(pstyle->stroke_width), + dash); HPDF_Page_MoveTo(pdf_page, x0, page_height - y0); HPDF_Page_LineTo(pdf_page, x1, page_height - y1); diff --git a/desktop/save_text.c b/desktop/save_text.c index 791ae9201..a86f173b0 100644 --- a/desktop/save_text.c +++ b/desktop/save_text.c @@ -32,8 +32,8 @@ #include "utils/utf8.h" #include "utils/utils.h" #include "netsurf/content.h" -#include "render/box.h" -#include "render/html.h" +#include "html/box.h" +#include "html/html_save.h" #include "netsurf/utf8.h" #include "desktop/gui_internal.h" diff --git a/desktop/scrollbar.c b/desktop/scrollbar.c index 5a7420ae1..35daef107 100644 --- a/desktop/scrollbar.c +++ b/desktop/scrollbar.c @@ -29,6 +29,7 @@ #include "utils/log.h" #include "utils/messages.h" #include "utils/utils.h" +#include "utils/nscolour.h" #include "utils/nsoption.h" #include "netsurf/browser_window.h" #include "netsurf/mouse.h" @@ -164,17 +165,17 @@ scrollbar_rectangle(const struct redraw_context *ctx, static plot_style_t c0 = { .stroke_type = PLOT_OP_TYPE_SOLID, - .stroke_width = 1, + .stroke_width = plot_style_int_to_fixed(1), }; static plot_style_t c1 = { .stroke_type = PLOT_OP_TYPE_SOLID, - .stroke_width = 1, + .stroke_width = plot_style_int_to_fixed(1), }; static plot_style_t c2 = { .stroke_type = PLOT_OP_TYPE_SOLID, - .stroke_width = 1, + .stroke_width = plot_style_int_to_fixed(1), }; if (inset) { @@ -250,29 +251,17 @@ scrollbar_redraw(struct scrollbar *s, plot_style_t bg_fill_style = { .fill_type = PLOT_OP_TYPE_SOLID, + .fill_colour = nscolours[NSCOLOUR_SCROLL_WELL], }; plot_style_t fg_fill_style = { .fill_type = PLOT_OP_TYPE_SOLID, + .fill_colour = nscolours[NSCOLOUR_BUTTON_BG], }; plot_style_t arrow_fill_style = { .fill_type = PLOT_OP_TYPE_SOLID, + .fill_colour = nscolours[NSCOLOUR_BUTTON_FG], }; - res = ns_system_colour_char("Scrollbar", &bg_fill_style.fill_colour); - if (res != NSERROR_OK) { - return res; - } - - res = ns_system_colour_char("ButtonFace", &fg_fill_style.fill_colour); - if (res != NSERROR_OK) { - return res; - } - - res = ns_system_colour_char("ButtonText", &arrow_fill_style.fill_colour); - if (res != NSERROR_OK) { - return res; - } - area.x0 = x; area.y0 = y; area.x1 = x + (s->horizontal ? s->length : SCROLLBAR_WIDTH) - 1; @@ -573,11 +562,11 @@ void scrollbar_set(struct scrollbar *s, int value, bool bar_pos) bool scrollbar_scroll(struct scrollbar *s, int change) { int well_length; - int old_offset = s->offset; + int old_offset; struct scrollbar_msg_data msg; - if (change == 0 || s->full_size <= s->visible_size) { - /* zero scroll step, or unscrollable */ + if (s == NULL || change == 0 || s->full_size <= s->visible_size) { + /* scrollbar not present, zero scroll step, or unscrollable */ return false; } @@ -605,6 +594,7 @@ bool scrollbar_scroll(struct scrollbar *s, int change) } /* Get new offset */ + old_offset = s->offset; if (s->offset + change > s->full_size - s->visible_size) { s->offset = s->full_size - s->visible_size; } else if (s->offset + change < 0) { diff --git a/desktop/scrollbar.h b/desktop/scrollbar.h index fa5e167f2..796520724 100644 --- a/desktop/scrollbar.h +++ b/desktop/scrollbar.h @@ -38,6 +38,7 @@ #define SCROLL_BOTTOM INT_MAX struct scrollbar; +struct redraw_context; /** * scrollbar message types diff --git a/desktop/search.c b/desktop/search.c index 201d416dd..e21f520a6 100644 --- a/desktop/search.c +++ b/desktop/search.c @@ -18,11 +18,17 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - /** \file +/** + * \file * Free text search (core) */ -#include "content/content.h" +#include <stdbool.h> + +#include "utils/errors.h" +#include "content/textsearch.h" +#include "netsurf/types.h" +#include "netsurf/browser_window.h" #include "desktop/browser_private.h" #include "desktop/search.h" @@ -33,7 +39,7 @@ void browser_window_search(struct browser_window *bw, void *context, { if ((bw != NULL) && (bw->current_content != NULL)) { - content_search(bw->current_content, context, flags, string); + content_textsearch(bw->current_content, context, flags, string); } } @@ -42,6 +48,6 @@ void browser_window_search_clear(struct browser_window *bw) { if ((bw != NULL) && (bw->current_content != NULL)) { - content_search_clear(bw->current_content); + content_textsearch_clear(bw->current_content); } } diff --git a/desktop/search.h b/desktop/search.h index baf382e77..c39d1d8a3 100644 --- a/desktop/search.h +++ b/desktop/search.h @@ -16,11 +16,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef _NETSURF_DESKTOP_SEARCH_H_ -#define _NETSURF_DESKTOP_SEARCH_H_ +/** + * \file + * Browseing window text search interface + */ -#include <ctype.h> -#include <string.h> +#ifndef NETSURF_DESKTOP_SEARCH_H_ +#define NETSURF_DESKTOP_SEARCH_H_ struct browser_window; diff --git a/desktop/searchweb.c b/desktop/searchweb.c index c07cac9d5..b78c43f69 100644 --- a/desktop/searchweb.c +++ b/desktop/searchweb.c @@ -17,11 +17,12 @@ */ /** - * \file desktop/searchweb.c + * \file * \brief core web search facilities implementation. */ #include <stdlib.h> +#include <string.h> #include "utils/utils.h" #include "utils/log.h" @@ -52,7 +53,7 @@ static struct search_web_ctx_s { } search_web_ctx; -static const char *default_providers = "Google|www.google.com|http://www.google.com/search?q=%s|http://www.google.com/favicon.ico|\n"; +static const char *default_providers = "Google|www.google.com|https://www.google.com/search?q=%s|https://www.google.com/favicon.ico|\n"; static const char *default_search_icon_url = "resource:icons/search.png"; @@ -298,10 +299,8 @@ search_web_ico_callback(hlcache_handle *ico, case CONTENT_MSG_ERROR: NSLOG(netsurf, INFO, "icon %s error: %s", nsurl_access(hlcache_handle_get_url(ico)), - event->data.error); - /* fall through */ + event->data.errordata.errormsg); - case CONTENT_MSG_ERRORCODE: hlcache_handle_release(ico); /* clear reference to released handle */ provider->ico_handle = NULL; @@ -334,11 +333,11 @@ search_web_omni(const char *term, } /* try with adding default scheme */ - eterm = malloc(strlen(term) + SLEN("http://") + 1); + eterm = malloc(strlen(term) + SLEN("https://") + 1); if (eterm == NULL) { return NSERROR_NOMEM; } - sprintf(eterm, "http://%s", term); + sprintf(eterm, "https://%s", term); ret = nsurl_create(eterm, &url); free(eterm); if (ret == NSERROR_OK) { @@ -368,7 +367,7 @@ search_web_omni(const char *term, } /* exported interface documented in desktop/searchweb.h */ -nserror search_web_select_provider(int selection) +nserror search_web_get_provider_bitmap(struct bitmap **bitmap_out) { struct search_provider *provider; struct bitmap *ico_bitmap = NULL; @@ -378,13 +377,44 @@ nserror search_web_select_provider(int selection) return NSERROR_INIT_FAILED; } + provider = &search_web_ctx.providers[search_web_ctx.current]; + + /* set the icon now (if we can) at least to the default */ + if (provider->ico_handle != NULL) { + ico_bitmap = content_get_bitmap(provider->ico_handle); + } + if ((ico_bitmap == NULL) && + (search_web_ctx.default_ico_handle != NULL)) { + ico_bitmap = content_get_bitmap(search_web_ctx.default_ico_handle); + } + + *bitmap_out = ico_bitmap; + return NSERROR_OK; +} + + +/* exported interface documented in desktop/searchweb.h */ +nserror search_web_select_provider(const char *selection) +{ + struct search_provider *provider; + struct bitmap *ico_bitmap = NULL; + size_t pidx; + + /* must be initialised */ + if (search_web_ctx.providers == NULL) { + return NSERROR_INIT_FAILED; + } + /* negative value just selects whatevers current */ - if (selection >= 0) { - /* ensure selection lies within acceptable range */ - if ((size_t)selection < search_web_ctx.providers_count) { - search_web_ctx.current = selection; - } else { - /* out of range */ + if (selection != NULL) { + for (pidx=0; pidx < search_web_ctx.providers_count;pidx++) { + if (strcmp(search_web_ctx.providers[pidx].name, selection)==0) { + search_web_ctx.current = pidx; + break; + } + } + if (pidx == search_web_ctx.providers_count) { + /* selected provider not found */ search_web_ctx.current = 0; } } @@ -399,12 +429,12 @@ nserror search_web_select_provider(int selection) (search_web_ctx.default_ico_handle != NULL)) { ico_bitmap = content_get_bitmap(search_web_ctx.default_ico_handle); } - /* update the callback with the provider change. Bitmap may + + /* signal the frontend with the provider change. Bitmap may * be NULL at this point. */ guit->search_web->provider_update(provider->name, ico_bitmap); - /* if the providers icon has not been retrieved get it now */ if (provider->ico_handle == NULL) { nsurl *icon_nsurl; @@ -458,10 +488,8 @@ default_ico_callback(hlcache_handle *ico, case CONTENT_MSG_ERROR: NSLOG(netsurf, INFO, "icon %s error: %s", nsurl_access(hlcache_handle_get_url(ico)), - event->data.error); - /* fall through */ + event->data.errordata.errormsg); - case CONTENT_MSG_ERRORCODE: hlcache_handle_release(ico); /* clear reference to released handle */ ctx->default_ico_handle = NULL; @@ -475,17 +503,21 @@ default_ico_callback(hlcache_handle *ico, } /* exported interface documented in desktop/searchweb.h */ -ssize_t search_web_iterate_providers(ssize_t from, const char **name) +ssize_t search_web_iterate_providers(ssize_t iter, const char **name) { - if (from < 0) - return -1; - - if ((size_t)from >= search_web_ctx.providers_count) - return -1; + if (iter < 0) { + iter=0; + } else { + iter++; + } - *name = search_web_ctx.providers[from].name; + if ((size_t)iter >= search_web_ctx.providers_count) { + iter = -1; + } else { + *name = search_web_ctx.providers[iter].name; + } - return from + 1; + return iter; } @@ -524,10 +556,14 @@ nserror search_web_init(const char *provider_fname) } /* get default search icon */ - ret = hlcache_handle_retrieve(icon_nsurl, 0, NULL, NULL, + ret = hlcache_handle_retrieve(icon_nsurl, + 0, + NULL, + NULL, default_ico_callback, &search_web_ctx, - NULL, CONTENT_IMAGE, + NULL, + CONTENT_IMAGE, &search_web_ctx.default_ico_handle); nsurl_unref(icon_nsurl); if (ret != NSERROR_OK) { diff --git a/desktop/searchweb.h b/desktop/searchweb.h index a96e7787b..d7e6a1c65 100644 --- a/desktop/searchweb.h +++ b/desktop/searchweb.h @@ -62,7 +62,7 @@ enum search_web_omni_flags { * term. The flags allow control over the operation. By default the * operations are: * - interpret the \a term as a url - * - if missing a scheme as a http: url + * - if missing a scheme as a https: url * - combined with the search providers url into a url for that provider. * * \param term The search term. @@ -72,37 +72,48 @@ enum search_web_omni_flags { */ nserror search_web_omni(const char *term, enum search_web_omni_flags flags, struct nsurl **url_out); + +/** + * obtain the current providers bitmap + * + * obtain the icon representing the current web search provider + * + * \param bitmap_out recives the resulting bitmap which may be NULL + * \return NSERROR_OK on success or NSERROR_INIT_FAILED if not initialised + */ +nserror search_web_get_provider_bitmap(struct bitmap **bitmap_out); + /** * Change the currently selected web search provider. * - * \param selection Index of the search provider to select or -1 to + * \param selection Name of the search provider to select or NULL to * reselect the current provider * \return NSERROR_OK on success or appropriate error code. */ -nserror search_web_select_provider(int selection); +nserror search_web_select_provider(const char *selection); /** * Iterate the search providers, returning their names. * - * \param from Index to start iteration from. Use 0 to begin iteration. + * \param from Index to start iteration from. Use -1 to begin iteration. * Use the value returned from search_web_iterate_providers to * continue an iteration. * \param name Pointer to fill in with the search provider name requested. * \return -1 if there are no more, otherwise the iterator for the next item. * * \verbatim - * ssize_t iter; + * ssize_t iter = -1; * const char *name; * ... - * for (iter = search_web_iterate_providers(0, &name); - * iter != -1; - * iter = search_web_iterate_providers(iter, &name)) { + * iter = search_web_iterate_providers(iter, &name); + * while(iter !=-1) { * do_something_with(name); + * iter = search_web_iterate_providers(iter, &name); * } * \endverbatim */ -ssize_t search_web_iterate_providers(ssize_t from, const char **name); +ssize_t search_web_iterate_providers(ssize_t iter, const char **name); /** diff --git a/desktop/selection.c b/desktop/selection.c index 7506af0ef..8b1f127c4 100644 --- a/desktop/selection.c +++ b/desktop/selection.c @@ -17,47 +17,24 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** \file - * Text selection within browser windows (implementation). - */ +/** + * \file + * implementation of text selection within browser windows. + */ -#include <assert.h> -#include <stdio.h> #include <stdlib.h> -#include <stdbool.h> #include <string.h> -#include <dom/dom.h> -#include "utils/log.h" -#include "utils/utf8.h" +#include "netsurf/clipboard.h" +#include "netsurf/browser_window.h" +#include "netsurf/window.h" #include "utils/utils.h" -#include "netsurf/form.h" -#include "render/box.h" -#include "render/html_internal.h" -#include "render/font.h" -#include "render/textplain.h" +#include "content/content_protected.h" -#include "netsurf/mouse.h" #include "desktop/browser_private.h" -#include "netsurf/plotters.h" -#include "desktop/save_text.h" -#include "desktop/selection.h" -#include "netsurf/clipboard.h" -#include "netsurf/window.h" #include "desktop/gui_internal.h" +#include "desktop/selection.h" -/** - * Text selection works by labelling each node in the box tree with its - * start index in the textual representation of the tree's content. - */ - -#define SPACE_LEN(b) ((b->space == 0) ? 0 : 1) - - -struct rdw_info { - bool inited; - struct rect r; -}; struct selection_string { char *buffer; @@ -69,731 +46,414 @@ struct selection_string { }; -typedef bool (*seln_traverse_handler)(const char *text, size_t length, - struct box *box, void *handle, const char *whitespace_text, - size_t whitespace_length); +typedef enum { + DRAG_NONE, + DRAG_START, + DRAG_END +} seln_drag_state; +struct selection { + struct content *c; -static bool redraw_handler(const char *text, size_t length, struct box *box, - void *handle, const char *whitespace_text, - size_t whitespace_length); -static void selection_redraw(struct selection *s, unsigned start_idx, - unsigned end_idx); -static bool selected_part(struct box *box, unsigned start_idx, unsigned end_idx, - unsigned *start_offset, unsigned *end_offset); -static bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, - seln_traverse_handler handler, - void *handle, save_text_whitespace *before, bool *first, - bool do_marker); -static unsigned selection_label_subtree(struct box *box, unsigned idx); + unsigned max_idx; /* total bytes in text representation */ -/** - * Get the browser window containing the content a selection object belongs to. - * - * \param s selection object - * \return the browser window - */ -static struct browser_window * selection_get_browser_window(struct selection *s) -{ - if (s->is_html) - return html_get_browser_window(s->c); - else - return textplain_get_browser_window(s->c); -} - - -/** - * Creates a new selection object associated with a browser window. - * - * \return new selection context - */ + unsigned start_idx; /* offset in bytes within text representation */ + unsigned end_idx; -struct selection *selection_create(struct content *c, bool is_html) -{ - struct selection *s = calloc(1, sizeof(struct selection)); - if (s) { - selection_prepare(s, c, is_html); - } + bool defined; - return s; -} + seln_drag_state drag_state; +}; /** - * Prepare a newly created selection object for use. + * Redraws the given range of text. * - * \param s selection object - * \param c content - * \param is_html true if content is html false if content is textplain + * \param s selection object + * \param start_idx start offset (bytes) within the textual representation + * \param end_idx end offset (bytes) within the textual representation */ - -void selection_prepare(struct selection *s, struct content *c, bool is_html) +static nserror +selection_redraw(struct selection *s, unsigned start_idx, unsigned end_idx) { - if (s) { - s->c = c; - s->is_html = is_html; - s->root = NULL; - s->drag_state = DRAG_NONE; - s->max_idx = 0; - selection_clear(s, false); - } -} + nserror res; + if (s->c->handler->textselection_redraw != NULL) { + res = s->c->handler->textselection_redraw(s->c, + start_idx, + end_idx); + } else { + res = NSERROR_NOT_IMPLEMENTED; + } -/** - * Destroys a selection object, without updating the - * owning window (caller should call selection_clear() - * first if update is desired) - * - * \param s selection object - */ - -void selection_destroy(struct selection *s) -{ - if (s != NULL) - free(s); + return res; } /** - * Initialise the selection object to use the given box subtree as its root, - * ie. selections are confined to that subtree, whilst maintaining the current - * selection whenever possible because, for example, it's just the page being - * resized causing the layout to change. + * Set the start position of the current selection, updating the screen. * - * \param s selection object - * \param root the root box for html document or NULL for text/plain + * \param s selection object + * \param offset byte offset within textual representation */ - -void selection_reinit(struct selection *s, struct box *root) +static void selection_set_start(struct selection *s, unsigned offset) { - unsigned root_idx; - - assert(s); + bool was_defined; + unsigned old_start; - root_idx = 0; + old_start = s->start_idx; + s->start_idx = offset; - s->root = root; - if (root) { - s->max_idx = selection_label_subtree(root, root_idx); - } else { - if (s->is_html == false) - s->max_idx = textplain_size(s->c); - else - s->max_idx = 0; - } + was_defined = s->defined; + s->defined = (s->start_idx < s->end_idx); - if (s->defined) { - if (s->end_idx > s->max_idx) s->end_idx = s->max_idx; - if (s->start_idx > s->max_idx) s->start_idx = s->max_idx; - s->defined = (s->end_idx > s->start_idx); + if (was_defined) { + if (offset < old_start) { + selection_redraw(s, s->start_idx, old_start); + } else { + selection_redraw(s, old_start, s->start_idx); + } + } else if (s->defined) { + selection_redraw(s, s->start_idx, s->end_idx); } } /** - * Initialise the selection object to use the given box subtree as its root, - * ie. selections are confined to that subtree. + * Set the end position of the current selection, updating the screen. * - * \param s selection object - * \param root the root box for html document or NULL for text/plain + * \param s selection object + * \param offset byte offset within textual representation */ - -void selection_init(struct selection *s, struct box *root) +static void selection_set_end(struct selection *s, unsigned offset) { - if (s->defined) - selection_clear(s, true); + bool was_defined; + unsigned old_end; - s->defined = false; - s->start_idx = 0; - s->end_idx = 0; - s->drag_state = DRAG_NONE; + old_end = s->end_idx; + s->end_idx = offset; + + was_defined = s->defined; + s->defined = (s->start_idx < s->end_idx); - selection_reinit(s, root); + if (was_defined) { + if (offset < old_end) { + selection_redraw(s, s->end_idx, old_end); + } else { + selection_redraw(s, old_end, s->end_idx); + } + } else if (s->defined) { + selection_redraw(s, s->start_idx, s->end_idx); + } } /** - * Label each text box in the given box subtree with its position - * in a textual representation of the content. + * Traverse the current selection, calling the handler function (with its + * handle) for all boxes that lie (partially) within the given range * - * \param box The box at root of subtree - * \param idx current position within textual representation - * \return updated position + * \param s The selection context. + * \param handler handler function to call + * \param handle handle to pass + * \return false iff traversal abandoned part-way through */ - -unsigned selection_label_subtree(struct box *box, unsigned idx) +static bool +selection_copy(struct selection *s, struct selection_string *selstr) { - struct box *child = box->children; - - box->byte_offset = idx; + nserror res; - if (box->text) - idx += box->length + SPACE_LEN(box); - - while (child) { - if (child->list_marker) - idx = selection_label_subtree(child->list_marker, idx); - - idx = selection_label_subtree(child, idx); - child = child->next; + if (s->c->handler->textselection_copy != NULL) { + res = s->c->handler->textselection_copy(s->c, + s->start_idx, + s->end_idx, + selstr); + } else { + res = NSERROR_NOT_IMPLEMENTED; } - return idx; + if (res != NSERROR_OK) { + return false; + } + return true; } /** - * Handles mouse clicks (including drag starts) in or near a selection - * - * \param s selection object - * \param mouse state of mouse buttons and modifier keys - * \param idx byte offset within textual representation + * Append text to selection string. * - * \return true iff the click has been handled by the selection code + * \param text text to be added + * \param length length of text in bytes + * \param space indicates whether a trailing space should be appended + * \param style The font style to use. + * \param sel_string string to append to, may be resized + * \return true iff successful */ - -bool selection_click(struct selection *s, browser_mouse_state mouse, - unsigned idx) +bool +selection_string_append(const char *text, + size_t length, + bool space, + plot_font_style_t *style, + struct selection_string *sel_string) { - browser_mouse_state modkeys = - (mouse & (BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_2)); - int pos = -1; /* 0 = inside selection, 1 = after it */ - struct browser_window *top = selection_get_browser_window(s); - top = browser_window_get_root(top); - - if (selection_defined(s)) { - if (idx > s->start_idx) { - if (idx <= s->end_idx) - pos = 0; - else - pos = 1; - } - } - - if (!pos && - ((mouse & BROWSER_MOUSE_DRAG_1) || - (modkeys && (mouse & BROWSER_MOUSE_DRAG_2)))) { - /* drag-saving selection */ - char *sel = selection_get_copy(s); - guit->window->drag_save_selection(top->window, sel); - free(sel); - } - else if (!modkeys) { - if (pos && (mouse & BROWSER_MOUSE_PRESS_1)) { - /* Clear the selection if mouse is pressed outside the - * selection, Otherwise clear on release (to allow for drags) */ - - selection_clear(s, true); - } else if (mouse & BROWSER_MOUSE_DRAG_1) { - /* start new selection drag */ + size_t new_length = sel_string->length + length + (space ? 1 : 0) + 1; - selection_clear(s, true); - - selection_set_start(s, idx); - selection_set_end(s, idx); + if (style != NULL) { + /* Add text run style */ + nsclipboard_styles *new_styles; - s->drag_state = DRAG_END; + if (sel_string->n_styles == 0) { + assert(sel_string->length == 0); + } - guit->window->start_selection(top->window); + new_styles = realloc(sel_string->styles, + (sel_string->n_styles + 1) * + sizeof(nsclipboard_styles)); + if (new_styles == NULL) { + return false; } - else if (mouse & BROWSER_MOUSE_DRAG_2) { - /* adjust selection, but only if there is one */ - if (!selection_defined(s)) - return false; /* ignore Adjust drags */ + sel_string->styles = new_styles; - if (pos >= 0) { - selection_set_end(s, idx); + sel_string->styles[sel_string->n_styles].style = *style; + sel_string->styles[sel_string->n_styles].start = + sel_string->length; - s->drag_state = DRAG_END; - } - else { - selection_set_start(s, idx); + sel_string->n_styles++; + } - s->drag_state = DRAG_START; - } + if (new_length > sel_string->buffer_len) { + /* Need to extend buffer */ + size_t new_alloc = new_length + (new_length / 4); + char *new_buff; - guit->window->start_selection(top->window); + new_buff = realloc(sel_string->buffer, new_alloc); + if (new_buff == NULL) { + return false; } - else if (mouse & BROWSER_MOUSE_CLICK_2) { - - /* ignore Adjust clicks when there's no selection */ - if (!selection_defined(s)) - return false; - if (pos >= 0) - selection_set_end(s, idx); - else - selection_set_start(s, idx); - s->drag_state = DRAG_NONE; - } - else - return false; - } - else { - /* not our problem */ - return false; + sel_string->buffer = new_buff; + sel_string->buffer_len = new_alloc; } - /* this mouse click is selection-related */ - return true; -} - - -/** - * Handles movements related to the selection, eg. dragging of start and - * end points. - * - * \param s selection object - * \param mouse state of mouse buttons and modifier keys - * \param idx byte offset within text representation - */ + /* Copy text onto end of existing text in buffer */ + memcpy(sel_string->buffer + sel_string->length, text, length); + sel_string->length += length; -void selection_track(struct selection *s, browser_mouse_state mouse, - unsigned idx) -{ - if (!mouse) { - s->drag_state = DRAG_NONE; + if (space) { + sel_string->buffer[sel_string->length++] = ' '; } - switch (s->drag_state) { - - case DRAG_START: - if (idx > s->end_idx) { - unsigned old_end = s->end_idx; - selection_set_end(s, idx); - selection_set_start(s, old_end); - s->drag_state = DRAG_END; - } - else - selection_set_start(s, idx); - break; - - case DRAG_END: - if (idx < s->start_idx) { - unsigned old_start = s->start_idx; - selection_set_start(s, idx); - selection_set_end(s, old_start); - s->drag_state = DRAG_START; - } - else - selection_set_end(s, idx); - break; + /* Ensure NULL termination */ + sel_string->buffer[sel_string->length] = '\0'; - default: - break; - } + return true; } -/** - * Tests whether a text box lies partially within the given range of - * byte offsets, returning the start and end indexes of the bytes - * that are enclosed. - * - * \param box box to be tested - * \param start_idx byte offset of start of range - * \param end_idx byte offset of end of range - * \param start_offset receives the start offset of the selected part - * \param end_offset receives the end offset of the selected part - * \return true iff the range encloses at least part of the box - */ - -bool selected_part(struct box *box, unsigned start_idx, unsigned end_idx, - unsigned *start_offset, unsigned *end_offset) +/* exported interface documented in desktop/selection.h */ +struct selection *selection_create(struct content *c) { - size_t box_length = box->length + SPACE_LEN(box); - - if (box_length > 0) { - if (box->byte_offset >= start_idx && - box->byte_offset + box_length <= end_idx) { - - /* fully enclosed */ - *start_offset = 0; - *end_offset = box_length; - return true; - } - else if (box->byte_offset + box_length > start_idx && - box->byte_offset < end_idx) { - /* partly enclosed */ - int offset = 0; - int len; - - if (box->byte_offset < start_idx) - offset = start_idx - box->byte_offset; - - len = box_length - offset; - - if (box->byte_offset + box_length > end_idx) - len = end_idx - (box->byte_offset + offset); - - *start_offset = offset; - *end_offset = offset + len; - - return true; - } + struct selection *sel; + sel = calloc(1, sizeof(struct selection)); + if (sel) { + sel->c = c; + sel->drag_state = DRAG_NONE; + sel->max_idx = 0; + selection_clear(sel, false); } - return false; -} + return sel; +} -/** - * Traverse the given box subtree, calling the handler function (with its handle) - * for all boxes that lie (partially) within the given range - * - * \param box box subtree - * \param start_idx start of range within textual representation (bytes) - * \param end_idx end of range - * \param handler handler function to call - * \param handle handle to pass - * \param before type of whitespace to place before next encountered text - * \param first whether this is the first box with text - * \param do_marker whether deal enter any marker box - * \return false iff traversal abandoned part-way through - */ -bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, - seln_traverse_handler handler, - void *handle, save_text_whitespace *before, bool *first, - bool do_marker) +/* exported interface documented in desktop/selection.h */ +void selection_destroy(struct selection *s) { - struct box *child; - const char *whitespace_text = ""; - size_t whitespace_length = 0; - - assert(box); - - /* If selection starts inside marker */ - if (box->parent && box->parent->list_marker == box && !do_marker) { - /* set box to main list element */ - box = box->parent; + if (s == NULL) { + return; } - /* If box has a list marker */ - if (box->list_marker) { - /* do the marker box before continuing with the rest of the - * list element */ - if (!traverse_tree(box->list_marker, start_idx, end_idx, - handler, handle, before, first, - true)) - return false; - } + selection_clear(s, true); + free(s); +} - /* we can prune this subtree, it's after the selection */ - if (box->byte_offset >= end_idx) - return true; - /* read before calling the handler in case it modifies the tree */ - child = box->children; +/* exported interface documented in desktop/selection.h */ +void selection_reinit(struct selection *s) +{ + s->max_idx = 0; - /* If nicely formatted output of the selected text is required, work - * out what whitespace should be placed before the next bit of text */ - if (before) { - save_text_solve_whitespace(box, first, before, &whitespace_text, - &whitespace_length); - } - else { - whitespace_text = NULL; - } - if (box->type != BOX_BR && - !((box->type == BOX_FLOAT_LEFT || - box->type == BOX_FLOAT_RIGHT) && - !box->text)) { - unsigned start_offset; - unsigned end_offset; - - if (selected_part(box, start_idx, end_idx, &start_offset, - &end_offset)) { - if (!handler(box->text + start_offset, min(box->length, - end_offset) - start_offset, - box, handle, whitespace_text, - whitespace_length)) - return false; - if (before) { - *first = false; - *before = WHITESPACE_NONE; - } - } + if (s->c->handler->textselection_get_end != NULL) { + s->c->handler->textselection_get_end(s->c, &s->max_idx); } - /* find the first child that could lie partially within the selection; - * this is important at the top-levels of the tree for pruning subtrees - * that lie entirely before the selection */ - - if (child) { - struct box *next = child->next; - - while (next && next->byte_offset < start_idx) { - child = next; - next = child->next; + if (s->defined) { + if (s->end_idx > s->max_idx) { + s->end_idx = s->max_idx; } - - while (child) { - /* read before calling the handler in case it modifies - * the tree */ - struct box *next = child->next; - - if (!traverse_tree(child, start_idx, end_idx, - handler, handle, before, first, false)) - return false; - - child = next; + if (s->start_idx > s->max_idx) { + s->start_idx = s->max_idx; } + s->defined = (s->end_idx > s->start_idx); } - - return true; } -/** - * Traverse the current selection, calling the handler function (with its - * handle) for all boxes that lie (partially) within the given range - * - * \param s The selection context. - * \param handler handler function to call - * \param handle handle to pass - * \return false iff traversal abandoned part-way through - */ - -static bool selection_traverse(struct selection *s, - seln_traverse_handler handler, void *handle) +/* exported interface documented in desktop/selection.h */ +void selection_init(struct selection *s) { - save_text_whitespace before = WHITESPACE_NONE; - bool first = true; - const char *text; - size_t length; - - if (!selection_defined(s)) - return true; /* easy case, nothing to do */ - - if (s->root) { - /* HTML */ - return traverse_tree(s->root, s->start_idx, s->end_idx, - handler, handle, &before, &first, false); + if (s->defined) { + selection_clear(s, true); } - /* Text */ - text = textplain_get_raw_data(s->c, s->start_idx, s->end_idx, &length); - - if (text && !handler(text, length, NULL, handle, NULL, 0)) - return false; + s->defined = false; + s->start_idx = 0; + s->end_idx = 0; + s->drag_state = DRAG_NONE; - return true; + selection_reinit(s); } -/** - * Selection traversal handler for redrawing the screen when the selection - * has been altered. - * - * \param text pointer to text string - * \param length length of text to be appended (bytes) - * \param box pointer to text box being (partially) added - * \param handle unused handle, we don't need one - * \param whitespace_text whitespace to place before text for formatting - * may be NULL - * \param whitespace_length length of whitespace_text - * \return true iff successful and traversal should continue - */ - -bool redraw_handler(const char *text, size_t length, struct box *box, - void *handle, const char *whitespace_text, - size_t whitespace_length) +/* exported interface documented in desktop/selection.h */ +bool +selection_click(struct selection *s, + struct browser_window *top, + browser_mouse_state mouse, + unsigned idx) { - if (box) { - struct rdw_info *r = (struct rdw_info*)handle; - int width, height; - int x, y; - plot_font_style_t fstyle; - - font_plot_style_from_css(box->style, &fstyle); - - /* \todo - it should be possible to reduce the redrawn area by - * considering the 'text', 'length' and 'space' parameters */ - box_coords(box, &x, &y); + browser_mouse_state modkeys; + int pos = -1; /* 0 = inside selection, 1 = after it */ - width = box->padding[LEFT] + box->width + box->padding[RIGHT]; - height = box->padding[TOP] + box->height + box->padding[BOTTOM]; + modkeys = (mouse & (BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_2)); - if (box->type == BOX_TEXT && box->space != 0) - width += box->space; + top = browser_window_get_root(top); - if (r->inited) { - if (x < r->r.x0) r->r.x0 = x; - if (y < r->r.y0) r->r.y0 = y; - if (x + width > r->r.x1) r->r.x1 = x + width; - if (y + height > r->r.y1) r->r.y1 = y + height; - } - else { - r->inited = true; - r->r.x0 = x; - r->r.y0 = y; - r->r.x1 = x + width; - r->r.y1 = y + height; + if (s->defined) { + if (idx > s->start_idx) { + if (idx <= s->end_idx) { + pos = 0; + } else { + pos = 1; + } } } - return true; -} + if (!pos && + ((mouse & BROWSER_MOUSE_DRAG_1) || + (modkeys && (mouse & BROWSER_MOUSE_DRAG_2)))) { + /* drag-saving selection */ + char *sel = selection_get_copy(s); + guit->window->drag_save_selection(top->window, sel); + free(sel); + } else if (!modkeys) { + if (pos && (mouse & BROWSER_MOUSE_PRESS_1)) { + /* Clear the selection if mouse is pressed + * outside the selection, Otherwise clear on + * release (to allow for drags) + */ -/** - * Redraws the given range of text. - * - * \param s selection object - * \param start_idx start offset (bytes) within the textual representation - * \param end_idx end offset (bytes) within the textual representation - */ + selection_clear(s, true); -void selection_redraw(struct selection *s, unsigned start_idx, unsigned end_idx) -{ - struct rdw_info rdw; + } else if (mouse & BROWSER_MOUSE_DRAG_1) { + /* start new selection drag */ - assert(end_idx >= start_idx); - rdw.inited = false; + selection_clear(s, true); - if (s->root) { - if (!traverse_tree(s->root, start_idx, end_idx, - redraw_handler, &rdw, - NULL, NULL, false)) - return; - } - else { - if (s->is_html == false && end_idx > start_idx) { - textplain_coords_from_range(s->c, start_idx, - end_idx, &rdw.r); - rdw.inited = true; - } - } + selection_set_start(s, idx); + selection_set_end(s, idx); - if (rdw.inited) - content__request_redraw(s->c, rdw.r.x0, rdw.r.y0, - rdw.r.x1 - rdw.r.x0, rdw.r.y1 - rdw.r.y0); -} + s->drag_state = DRAG_END; + guit->window->event(top->window, + GW_EVENT_START_SELECTION); -/** - * Append text to selection string. - * - * \param text text to be added - * \param length length of text in bytes - * \param space indicates whether a trailing space should be appended - * \param style The font style to use. - * \param sel_string string to append to, may be resized - * \return true iff successful - */ + } else if (mouse & BROWSER_MOUSE_DRAG_2) { -static bool selection_string_append(const char *text, size_t length, bool space, - plot_font_style_t *style, struct selection_string *sel_string) -{ - size_t new_length = sel_string->length + length + (space ? 1 : 0) + 1; + /* adjust selection, but only if there is one */ + if (!s->defined) { + return false; /* ignore Adjust drags */ + } - if (style != NULL) { - /* Add text run style */ - nsclipboard_styles *new_styles; + if (pos >= 0) { + selection_set_end(s, idx); - if (sel_string->n_styles == 0) - assert(sel_string->length == 0); + s->drag_state = DRAG_END; + } else { + selection_set_start(s, idx); - new_styles = realloc(sel_string->styles, - (sel_string->n_styles + 1) * - sizeof(nsclipboard_styles)); - if (new_styles == NULL) - return false; + s->drag_state = DRAG_START; + } - sel_string->styles = new_styles; + guit->window->event(top->window, + GW_EVENT_START_SELECTION); - sel_string->styles[sel_string->n_styles].style = *style; - sel_string->styles[sel_string->n_styles].start = - sel_string->length; + } else if (mouse & BROWSER_MOUSE_CLICK_2) { - sel_string->n_styles++; - } + /* ignore Adjust clicks when there's no selection */ + if (!s->defined) { + return false; + } - if (new_length > sel_string->buffer_len) { - /* Need to extend buffer */ - size_t new_alloc = new_length + (new_length / 4); - char *new_buff; + if (pos >= 0) { + selection_set_end(s, idx); + } else { + selection_set_start(s, idx); + } + s->drag_state = DRAG_NONE; - new_buff = realloc(sel_string->buffer, new_alloc); - if (new_buff == NULL) + } else { return false; + } - sel_string->buffer = new_buff; - sel_string->buffer_len = new_alloc; + } else { + /* not our problem */ + return false; } - /* Copy text onto end of existing text in buffer */ - memcpy(sel_string->buffer + sel_string->length, text, length); - sel_string->length += length; - - if (space) - sel_string->buffer[sel_string->length++] = ' '; - - /* Ensure NULL termination */ - sel_string->buffer[sel_string->length] = '\0'; - + /* this mouse click is selection-related */ return true; } -/** - * Selection traversal routine for appending text to a string - * - * \param text pointer to text being added, or NULL for newline - * \param length length of text to be appended (bytes) - * \param box pointer to text box, or NULL if from textplain - * \param handle selection string to append to - * \param whitespace_text whitespace to place before text for formatting - * may be NULL - * \param whitespace_length length of whitespace_text - * \return true iff successful and traversal should continue - */ - -static bool selection_copy_handler(const char *text, size_t length, - struct box *box, void *handle, const char *whitespace_text, - size_t whitespace_length) +/* exported interface documented in desktop/selection.h */ +void +selection_track(struct selection *s, browser_mouse_state mouse, unsigned idx) { - bool add_space = false; - plot_font_style_t style; - plot_font_style_t *pstyle = NULL; - - /* add any whitespace which precedes the text from this box */ - if (whitespace_text != NULL && whitespace_length > 0) { - if (!selection_string_append(whitespace_text, - whitespace_length, false, pstyle, handle)) { - return false; - } + if (!mouse) { + s->drag_state = DRAG_NONE; } - if (box != NULL) { - /* HTML */ - add_space = (box->space != 0); + switch (s->drag_state) { - if (box->style != NULL) { - /* Override default font style */ - font_plot_style_from_css(box->style, &style); - pstyle = &style; + case DRAG_START: + if (idx > s->end_idx) { + unsigned old_end = s->end_idx; + selection_set_end(s, idx); + selection_set_start(s, old_end); + s->drag_state = DRAG_END; } else { - /* If there's no style, there must be no text */ - assert(box->text == NULL); + selection_set_start(s, idx); } - } + break; - /* add the text from this box */ - if (!selection_string_append(text, length, add_space, pstyle, handle)) - return false; + case DRAG_END: + if (idx < s->start_idx) { + unsigned old_start = s->start_idx; + selection_set_start(s, idx); + selection_set_end(s, old_start); + s->drag_state = DRAG_START; + } else { + selection_set_end(s, idx); + } + break; - return true; + default: + break; + } } -/** - * Get copy of selection as string - * - * \param s selection - * \return string of selected text, or NULL. Ownership passed to caller. - */ - +/* exported interface documented in desktop/selection.h */ char *selection_get_copy(struct selection *s) { struct selection_string sel_string = { @@ -808,7 +468,7 @@ char *selection_get_copy(struct selection *s) if (s == NULL || !s->defined) return NULL; - if (!selection_traverse(s, selection_copy_handler, &sel_string)) { + if (!selection_copy(s, &sel_string)) { free(sel_string.buffer); free(sel_string.styles); return NULL; @@ -820,13 +480,7 @@ char *selection_get_copy(struct selection *s) } - -/** - * Copy the selected contents to the clipboard - * - * \param s selection - * \return true iff successful - */ +/* exported interface documented in desktop/selection.h */ bool selection_copy_to_clipboard(struct selection *s) { struct selection_string sel_string = { @@ -838,17 +492,20 @@ bool selection_copy_to_clipboard(struct selection *s) .styles = NULL }; - if (s == NULL || !s->defined) + if (s == NULL || !s->defined) { return false; + } - if (!selection_traverse(s, selection_copy_handler, &sel_string)) { + if (!selection_copy(s, &sel_string)) { free(sel_string.buffer); free(sel_string.styles); return false; } - guit->clipboard->set(sel_string.buffer, sel_string.length, - sel_string.styles, sel_string.n_styles); + guit->clipboard->set(sel_string.buffer, + sel_string.length, + sel_string.styles, + sel_string.n_styles); free(sel_string.buffer); free(sel_string.styles); @@ -857,21 +514,15 @@ bool selection_copy_to_clipboard(struct selection *s) } -/** - * Clears the current selection, optionally causing the screen to be updated. - * - * \param s selection object - * \param redraw true iff the previously selected region of the browser - * window should be redrawn - */ - -void selection_clear(struct selection *s, bool redraw) +/* exported interface documented in desktop/selection.h */ +bool selection_clear(struct selection *s, bool redraw) { int old_start, old_end; bool was_defined; assert(s); - was_defined = selection_defined(s); + + was_defined = s->defined; old_start = s->start_idx; old_end = s->end_idx; @@ -879,106 +530,75 @@ void selection_clear(struct selection *s, bool redraw) s->start_idx = 0; s->end_idx = 0; - if (redraw && was_defined) + if (redraw && was_defined) { selection_redraw(s, old_start, old_end); -} + } + return was_defined; +} -/** - * Selects all the text within the box subtree controlled by - * this selection object, updating the screen accordingly. - * - * \param s selection object - */ +/* exported interface documented in desktop/selection.h */ void selection_select_all(struct selection *s) { assert(s); s->defined = true; - + selection_set_start(s, 0); selection_set_end(s, s->max_idx); } -/** - * Set the start position of the current selection, updating the screen. - * - * \param s selection object - * \param offset byte offset within textual representation - */ - -void selection_set_start(struct selection *s, unsigned offset) +/* exported interface documented in desktop/selection.h */ +void selection_set_position(struct selection *s, unsigned start, unsigned end) { - bool was_defined = selection_defined(s); - unsigned old_start = s->start_idx; - - s->start_idx = offset; - s->defined = (s->start_idx < s->end_idx); - - if (was_defined) { - if (offset < old_start) - selection_redraw(s, s->start_idx, old_start); - else - selection_redraw(s, old_start, s->start_idx); - } - else if (selection_defined(s)) - selection_redraw(s, s->start_idx, s->end_idx); + selection_set_start(s, start); + selection_set_end(s, end); } -/** - * Set the end position of the current selection, updating the screen. - * - * \param s selection object - * \param offset byte offset within textual representation - */ - -void selection_set_end(struct selection *s, unsigned offset) +/* exported interface documented in desktop/selection.h */ +bool +selection_highlighted(const struct selection *s, + unsigned start, + unsigned end, + unsigned *start_idx, + unsigned *end_idx) { - bool was_defined = selection_defined(s); - unsigned old_end = s->end_idx; + assert(s); - s->end_idx = offset; - s->defined = (s->start_idx < s->end_idx); + if (!s->defined) { + return false; + } - if (was_defined) { - if (offset < old_end) - selection_redraw(s, s->end_idx, old_end); - else - selection_redraw(s, old_end, s->end_idx); + if ((end <= s->start_idx) || + (start >= s->end_idx)) { + return false; } - else if (selection_defined(s)) - selection_redraw(s, s->start_idx, s->end_idx); -} + *start_idx = (s->start_idx >= start) ? (s->start_idx - start) : 0; + *end_idx = min(end, s->end_idx) - start; -/** - * Tests whether a text range lies partially within the selection, if there is - * a selection defined, returning the start and end indexes of the bytes - * that should be selected. - * - * \param s the selection object - * \param start byte offset of start of text - * \param end byte offset of end of text - * \param start_idx receives the start index (in bytes) of the highlighted portion - * \param end_idx receives the end index (in bytes) - * \return true iff part of the given box lies within the selection - */ + return true; +} -bool selection_highlighted(const struct selection *s, - unsigned start, unsigned end, - unsigned *start_idx, unsigned *end_idx) +/* exported interface documented in desktop/selection.h */ +bool selection_active(struct selection *s) { - /* caller should have checked first for efficiency */ - assert(s); - assert(selection_defined(s)); + return s->defined; +} - if (end <= s->start_idx || start >= s->end_idx) - return false; +bool selection_dragging(struct selection *s) +{ + return s->drag_state != DRAG_NONE; +} - *start_idx = (s->start_idx >= start) ? (s->start_idx - start) : 0; - *end_idx = min(end, s->end_idx) - start; +bool selection_dragging_start(struct selection *s) +{ + return s->drag_state == DRAG_START; +} - return true; +void selection_drag_end(struct selection *s) +{ + s->drag_state = DRAG_NONE; } diff --git a/desktop/selection.h b/desktop/selection.h index e2bc3b31d..8cd523fd8 100644 --- a/desktop/selection.h +++ b/desktop/selection.h @@ -20,81 +20,172 @@ * Text selection within browser windows (interface). */ -#ifndef _NETSURF_DESKTOP_SELECTION_H_ -#define _NETSURF_DESKTOP_SELECTION_H_ +#ifndef NETSURF_DESKTOP_SELECTION_H_ +#define NETSURF_DESKTOP_SELECTION_H_ #include <stdbool.h> #include "netsurf/mouse.h" struct box; +struct browser_window; +struct plot_font_style; +struct selection_string; +struct selection; +struct content; + +/** + * determine if a selecion is active + */ +bool selection_active(struct selection *s); -typedef enum { - DRAG_NONE, - DRAG_START, - DRAG_END -} seln_drag_state; - - -/* this structure should be treated as opaque outside selection.c - (it's defined here to accelerate selection_defined(s) for reduced - impact on redraw code) */ - -struct selection -{ - struct content *c; - struct box *root; - - unsigned max_idx; /* total bytes in text representation */ - - unsigned start_idx; /* offset in bytes within text representation */ - unsigned end_idx; +bool selection_dragging(struct selection *s); - bool defined; - bool is_html; +bool selection_dragging_start(struct selection *s); - seln_drag_state drag_state; -}; +/** + * Handles completion of a drag operation + */ +void selection_drag_end(struct selection *s); +/** + * Creates a new selection object associated with a browser window. + * + * Used from text and html content handlers + * + * \return new selection context + */ +struct selection *selection_create(struct content *c); -struct selection *selection_create(struct content *c, bool is_html); -void selection_prepare(struct selection *s, struct content *c, bool is_html); +/** + * Destroys a selection object clearing it if nesessary + * + * Used from content textsearch + * + * \param s selection object + */ void selection_destroy(struct selection *s); -void selection_init(struct selection *s, struct box *root); -void selection_reinit(struct selection *s, struct box *root); - -/* struct box *selection_root(struct selection *s); */ -#define selection_root(s) ((s)->root) - -/* bool selection_defined(struct selection *s); */ -#define selection_defined(s) ((s)->defined) +/** + * Initialise the selection object to use the given box subtree as its root, + * ie. selections are confined to that subtree. + * + * Used from text and html content handlers + * + * \param s selection object + */ +void selection_init(struct selection *s); -/* bool selection_dragging(struct selection *s); */ -#define selection_dragging(s) ((s)->drag_state != DRAG_NONE) +/** + * Initialise the selection object to use the given box subtree as its root, + * ie. selections are confined to that subtree, whilst maintaining the current + * selection whenever possible because, for example, it's just the page being + * resized causing the layout to change. + * + * Used from html content handler + * + * \param s selection object + */ +void selection_reinit(struct selection *s); -/* bool selection_dragging_start(struct selection *s); */ -#define selection_dragging_start(s) ((s)->drag_state == DRAG_START) +/** + * Clears the current selection, optionally causing the screen to be updated. + * + * Used from text and html content handlers + * + * \param s selection object + * \param redraw true iff the previously selected region of the browser + * window should be redrawn + * \return true if selection was cleared false if not + */ +bool selection_clear(struct selection *s, bool redraw); -void selection_clear(struct selection *s, bool redraw); +/** + * Selects all the text within the box subtree controlled by + * this selection object, updating the screen accordingly. + * + * Used from text and html content handlers + * + * \param s selection object + */ void selection_select_all(struct selection *s); -void selection_set_start(struct selection *s, unsigned idx); -void selection_set_end(struct selection *s, unsigned idx); +/** + * Set the position of the current selection, updating the screen. + * + * Used from content textsearch + * + * \param s selection object + * \param start byte offset within textual representation + * \param end byte offset within textual representation + */ +void selection_set_position(struct selection *s, unsigned start, unsigned end); -bool selection_click(struct selection *s, browser_mouse_state mouse, - unsigned idx); -void selection_track(struct selection *s, browser_mouse_state mouse, - unsigned idx); +/** + * Handles mouse clicks (including drag starts) in or near a selection + * + * Used from text and html content handlers + * + * \param s selection object + * \param mouse state of mouse buttons and modifier keys + * \param idx byte offset within textual representation + * \return true iff the click has been handled by the selection code + */ +bool selection_click(struct selection *s, struct browser_window *top, browser_mouse_state mouse, unsigned idx); +/** + * Handles movements related to the selection, eg. dragging of start and + * end points. + * + * Used from text and html content handlers + * + * \param s selection object + * \param mouse state of mouse buttons and modifier keys + * \param idx byte offset within text representation + */ +void selection_track(struct selection *s, browser_mouse_state mouse, unsigned idx); + +/** + * Copy the selected contents to the clipboard + * + * Used from text and html content handlers + * + * \param s selection + * \return true iff successful + */ bool selection_copy_to_clipboard(struct selection *s); -char * selection_get_copy(struct selection *s); -/** Handles completion of a drag operation */ -/* void selection_drag_end(struct selection *s); */ -#define selection_drag_end(s) ((s)->drag_state = DRAG_NONE) +/** + * Get copy of selection as string + * + * Used from text and html content handlers + * + * \param s selection + * \return string of selected text, or NULL. Ownership passed to caller. + */ +char *selection_get_copy(struct selection *s); + -bool selection_highlighted(const struct selection *s, - unsigned start, unsigned end, - unsigned *start_idx, unsigned *end_idx); +/** + * Tests whether a text range lies partially within the selection, if there is + * a selection defined, returning the start and end indexes of the bytes + * that should be selected. + * + * Used from text and html content handlers, content textsearch + * + * \param s the selection object + * \param start byte offset of start of text + * \param end byte offset of end of text + * \param start_idx receives the start index (in bytes) of the highlighted portion + * \param end_idx receives the end index (in bytes) + * \return true iff part of the given box lies within the selection + */ +bool selection_highlighted(const struct selection *s, unsigned start, unsigned end, unsigned *start_idx, unsigned *end_idx); + +bool +selection_string_append(const char *text, + size_t length, + bool space, + struct plot_font_style *style, + struct selection_string *sel_string); #endif diff --git a/desktop/sslcert_viewer.c b/desktop/sslcert_viewer.c deleted file mode 100644 index f40af5968..000000000 --- a/desktop/sslcert_viewer.c +++ /dev/null @@ -1,572 +0,0 @@ -/* - * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> - * Copyright 2013 Michael Drake <tlsa@netsurf-browser.org> - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/** - * \file - * SSL Certificate verification UI implementation - */ - -#include <assert.h> -#include <stdlib.h> - -#include "content/fetch.h" -#include "content/urldb.h" -#include "content/hlcache.h" -#include "desktop/sslcert_viewer.h" -#include "desktop/treeview.h" -#include "utils/messages.h" -#include "utils/log.h" -#include "utils/utils.h" - -/** - * ssl certificate viewer data fields - */ -enum sslcert_viewer_field { - SSLCERT_V_SUBJECT, - SSLCERT_V_SERIAL, - SSLCERT_V_TYPE, - SSLCERT_V_VALID_UNTIL, - SSLCERT_V_VALID_FROM, - SSLCERT_V_VERSION, - SSLCERT_V_ISSUER, - SSLCERT_V_CERTIFICATES, - SSLCERT_V_N_FIELDS -}; - - -/** - * ssl certificate verification context. - */ -struct sslcert_session_data { - struct ssl_cert_info *certs; /**< Certificates */ - unsigned long num; /**< Number of certificates in chain */ - nsurl *url; /**< The url of the certificate */ - llcache_query_response cb; /**< Cert accept/reject callback */ - void *cbpw; /**< Context passed to callback */ - - treeview *tree; /**< The treeview object */ - struct treeview_field_desc fields[SSLCERT_V_N_FIELDS]; -}; - - -/** - * ssl certificate tree entry - */ -struct sslcert_entry { - treeview_node *entry; - char version[24]; - char serial[24]; - char type[24]; - struct treeview_field_data data[SSLCERT_V_N_FIELDS - 1]; -}; - - -/** - * Free a ssl certificate viewer entry's treeview field data. - * - * \param e Entry to free data from - */ -static void sslcert_viewer_free_treeview_field_data(struct sslcert_entry *e) -{ -} - - -/** - * Build a sslcert viewer treeview field from given text - * - * \param field SSL certificate treeview field to build - * \param data SSL certificate entry field data to set - * \param value Text to set in field, ownership yielded - * \param ssl_d SSL certificate session data - * \return NSERROR_OK on success, appropriate error otherwise - */ -static inline nserror -sslcert_viewer_field_builder(enum sslcert_viewer_field field, - struct treeview_field_data *data, - const char *value, - struct sslcert_session_data *ssl_d) -{ - data->field = ssl_d->fields[field].field; - data->value = value; - data->value_len = (value != NULL) ? strlen(value) : 0; - - return NSERROR_OK; -} - - -/** - * Set a sslcert viewer entry's data from the certificate. - * - * \param e Entry to set up - * \param cert Data associated with entry's certificate - * \param ssl_d SSL certificate session data - * \return NSERROR_OK on success, appropriate error otherwise - */ -static nserror -sslcert_viewer_set_treeview_field_data(struct sslcert_entry *e, - const struct ssl_cert_info *cert, - struct sslcert_session_data *ssl_d) -{ - unsigned int written; - - assert(e != NULL); - assert(cert != NULL); - assert(ssl_d != NULL); - - /* Set the fields up */ - sslcert_viewer_field_builder(SSLCERT_V_SUBJECT, - &e->data[SSLCERT_V_SUBJECT], - cert->subject, ssl_d); - - written = snprintf(e->serial, sizeof(e->serial), "%li", cert->serial); - assert(written < sizeof(e->serial)); - sslcert_viewer_field_builder(SSLCERT_V_SERIAL, - &e->data[SSLCERT_V_SERIAL], - e->serial, ssl_d); - - written = snprintf(e->type, sizeof(e->type), "%i", cert->cert_type); - assert(written < sizeof(e->type)); - sslcert_viewer_field_builder(SSLCERT_V_TYPE, - &e->data[SSLCERT_V_TYPE], - e->type, ssl_d); - - sslcert_viewer_field_builder(SSLCERT_V_VALID_UNTIL, - &e->data[SSLCERT_V_VALID_UNTIL], - cert->not_after, ssl_d); - - sslcert_viewer_field_builder(SSLCERT_V_VALID_FROM, - &e->data[SSLCERT_V_VALID_FROM], - cert->not_before, ssl_d); - - written = snprintf(e->version, sizeof(e->version), - "%li", cert->version); - assert(written < sizeof(e->version)); - sslcert_viewer_field_builder(SSLCERT_V_VERSION, - &e->data[SSLCERT_V_VERSION], - e->version, ssl_d); - - sslcert_viewer_field_builder(SSLCERT_V_ISSUER, - &e->data[SSLCERT_V_ISSUER], - cert->issuer, ssl_d); - - return NSERROR_OK; -} - - -/** - * Create a treeview node for a certificate - * - * \param ssl_d SSL certificate session data - * \param n Number of SSL certificate in chain, to make node for - * \return NSERROR_OK on success otherwise error code. - */ -static nserror -sslcert_viewer_create_node(struct sslcert_session_data *ssl_d, int n) -{ - struct sslcert_entry *e; - const struct ssl_cert_info *cert = &(ssl_d->certs[n]); - nserror err; - - /* Create new certificate viewer entry */ - e = malloc(sizeof(struct sslcert_entry)); - if (e == NULL) { - return NSERROR_NOMEM; - } - - err = sslcert_viewer_set_treeview_field_data(e, cert, ssl_d); - if (err != NSERROR_OK) { - free(e); - return err; - } - - /* Create the new treeview node */ - err = treeview_create_node_entry(ssl_d->tree, &(e->entry), - NULL, TREE_REL_FIRST_CHILD, - e->data, e, TREE_OPTION_NONE); - if (err != NSERROR_OK) { - sslcert_viewer_free_treeview_field_data(e); - free(e); - return err; - } - - return NSERROR_OK; -} - - -/** - * Initialise the treeview entry fields - * - * \param ssl_d SSL certificate session data - * \return NSERROR_OK on success otherwise error code. - */ -static nserror sslcert_init_entry_fields(struct sslcert_session_data *ssl_d) -{ - int i; - const char *label; - - for (i = 0; i < SSLCERT_V_N_FIELDS; i++) - ssl_d->fields[i].field = NULL; - - ssl_d->fields[SSLCERT_V_SUBJECT].flags = TREE_FLAG_DEFAULT; - label = "TreeviewLabelSubject"; - label = messages_get(label); - if (lwc_intern_string(label, strlen(label), - &ssl_d->fields[SSLCERT_V_SUBJECT].field) != - lwc_error_ok) { - goto error; - } - - ssl_d->fields[SSLCERT_V_SERIAL].flags = TREE_FLAG_SHOW_NAME; - label = "TreeviewLabelSerial"; - label = messages_get(label); - if (lwc_intern_string(label, strlen(label), - &ssl_d->fields[SSLCERT_V_SERIAL].field) != - lwc_error_ok) { - goto error; - } - - ssl_d->fields[SSLCERT_V_TYPE].flags = TREE_FLAG_SHOW_NAME; - label = "TreeviewLabelType"; - label = messages_get(label); - if (lwc_intern_string(label, strlen(label), - &ssl_d->fields[SSLCERT_V_TYPE].field) != - lwc_error_ok) { - goto error; - } - - ssl_d->fields[SSLCERT_V_VALID_UNTIL].flags = TREE_FLAG_SHOW_NAME; - label = "TreeviewLabelValidUntil"; - label = messages_get(label); - if (lwc_intern_string(label, strlen(label), - &ssl_d->fields[SSLCERT_V_VALID_UNTIL].field) != - lwc_error_ok) { - goto error; - } - - ssl_d->fields[SSLCERT_V_VALID_FROM].flags = TREE_FLAG_SHOW_NAME; - label = "TreeviewLabelValidFrom"; - label = messages_get(label); - if (lwc_intern_string(label, strlen(label), - &ssl_d->fields[SSLCERT_V_VALID_FROM].field) != - lwc_error_ok) { - goto error; - } - - ssl_d->fields[SSLCERT_V_VERSION].flags = TREE_FLAG_SHOW_NAME; - label = "TreeviewLabelVersion"; - label = messages_get(label); - if (lwc_intern_string(label, strlen(label), - &ssl_d->fields[SSLCERT_V_VERSION].field) != - lwc_error_ok) { - goto error; - } - - ssl_d->fields[SSLCERT_V_ISSUER].flags = TREE_FLAG_SHOW_NAME; - label = "TreeviewLabelIssuer"; - label = messages_get(label); - if (lwc_intern_string(label, strlen(label), - &ssl_d->fields[SSLCERT_V_ISSUER].field) != - lwc_error_ok) { - goto error; - } - - ssl_d->fields[SSLCERT_V_CERTIFICATES].flags = TREE_FLAG_DEFAULT; - label = "TreeviewLabelCertificates"; - label = messages_get(label); - if (lwc_intern_string(label, strlen(label), - &ssl_d->fields[SSLCERT_V_CERTIFICATES].field) != - lwc_error_ok) { - return false; - } - - return NSERROR_OK; - -error: - for (i = 0; i < SSLCERT_V_N_FIELDS; i++) - if (ssl_d->fields[i].field != NULL) - lwc_string_unref(ssl_d->fields[i].field); - - return NSERROR_UNKNOWN; -} - - -/** - * Delete ssl certificate viewer entries - * - * \param e Entry to delete. - */ -static void sslcert_viewer_delete_entry(struct sslcert_entry *e) -{ - sslcert_viewer_free_treeview_field_data(e); - free(e); -} - - -/** - * folder operation callback - * - * \param msg treeview message - * \param data message context - * \return NSERROR_OK on success - */ -static nserror -sslcert_viewer_tree_node_folder_cb(struct treeview_node_msg msg, void *data) -{ - switch (msg.msg) { - case TREE_MSG_NODE_DELETE: - case TREE_MSG_NODE_EDIT: - case TREE_MSG_NODE_LAUNCH: - break; - } - - return NSERROR_OK; -} - - -/** - * node entry callback - * - * \param msg treeview message - * \param data message context - * \return NSERROR_OK on success - */ -static nserror -sslcert_viewer_tree_node_entry_cb(struct treeview_node_msg msg, void *data) -{ - struct sslcert_entry *e = data; - - switch (msg.msg) { - case TREE_MSG_NODE_DELETE: - e->entry = NULL; - sslcert_viewer_delete_entry(e); - break; - - case TREE_MSG_NODE_EDIT: - case TREE_MSG_NODE_LAUNCH: - break; - } - - return NSERROR_OK; -} - - -/** - * ssl certificate treeview callbacks - */ -struct treeview_callback_table sslv_tree_cb_t = { - .folder = sslcert_viewer_tree_node_folder_cb, - .entry = sslcert_viewer_tree_node_entry_cb -}; - - -/* Exported interface, documented in sslcert_viewer.h */ -nserror -sslcert_viewer_init(struct core_window_callback_table *cw_t, - void *core_window_handle, - struct sslcert_session_data *ssl_d) -{ - nserror err; - int cert_loop; - - assert(ssl_d != NULL); - - err = treeview_init(); - if (err != NSERROR_OK) { - return err; - } - - NSLOG(netsurf, INFO, "Building certificate viewer"); - - /* Init. certificate chain treeview entry fields */ - err = sslcert_init_entry_fields(ssl_d); - if (err != NSERROR_OK) { - ssl_d->tree = NULL; - return err; - } - - /* Create the certificate treeview */ - err = treeview_create(&ssl_d->tree, &sslv_tree_cb_t, - SSLCERT_V_N_FIELDS, ssl_d->fields, - cw_t, core_window_handle, TREEVIEW_READ_ONLY); - if (err != NSERROR_OK) { - ssl_d->tree = NULL; - return err; - } - - /* Build treeview nodes from certificate chain */ - for (cert_loop = ssl_d->num - 1; cert_loop >= 0; cert_loop--) { - err = sslcert_viewer_create_node(ssl_d, cert_loop); - if (err != NSERROR_OK) { - return err; - } - } - - NSLOG(netsurf, INFO, "Built certificate viewer"); - - return NSERROR_OK; -} - - -/** - * Free SSL certificate session data - * - * \param ssl_d SSL certificate session data - */ -static void sslcert_cleanup_session(struct sslcert_session_data *ssl_d) -{ - assert(ssl_d != NULL); - - if (ssl_d->url) { - nsurl_unref(ssl_d->url); - ssl_d->url = NULL; - } - - if (ssl_d->certs) { - free(ssl_d->certs); - ssl_d->certs = NULL; - } - - free(ssl_d); -} - - -/* Exported interface, documented in sslcert_viewer.h */ -nserror sslcert_viewer_fini(struct sslcert_session_data *ssl_d) -{ - int i; - nserror err; - - NSLOG(netsurf, INFO, "Finalising ssl certificate viewer"); - - /* Destroy the treeview */ - err = treeview_destroy(ssl_d->tree); - - /* Free treeview entry fields */ - for (i = 0; i < SSLCERT_V_N_FIELDS; i++) - if (ssl_d->fields[i].field != NULL) - lwc_string_unref(ssl_d->fields[i].field); - - /* Destroy the sslcert_session_data */ - sslcert_cleanup_session(ssl_d); - - err = treeview_fini(); - if (err != NSERROR_OK) { - return err; - } - - NSLOG(netsurf, INFO, "Finalised ssl certificate viewer"); - - return err; -} - - -/* Exported interface, documented in sslcert_viewer.h */ -nserror -sslcert_viewer_create_session_data(unsigned long num, - nsurl *url, - llcache_query_response cb, - void *cbpw, - const struct ssl_cert_info *certs, - struct sslcert_session_data **ssl_d) -{ - struct sslcert_session_data *data; - - assert(url != NULL); - assert(certs != NULL); - - data = malloc(sizeof(struct sslcert_session_data)); - if (data == NULL) { - *ssl_d = NULL; - return NSERROR_NOMEM; - } - - /* copy certificate data */ - data->certs = malloc(num * sizeof(struct ssl_cert_info)); - if (data->certs == NULL) { - free(data); - *ssl_d = NULL; - return NSERROR_NOMEM; - } - memcpy(data->certs, certs, num * sizeof(struct ssl_cert_info)); - - data->url = nsurl_ref(url); - data->num = num; - data->cb = cb; - data->cbpw = cbpw; - - data->tree = NULL; - - *ssl_d = data; - return NSERROR_OK; -} - - -/* Exported interface, documented in sslcert_viewer.h */ -nserror sslcert_viewer_reject(struct sslcert_session_data *ssl_d) -{ - assert(ssl_d != NULL); - - ssl_d->cb(false, ssl_d->cbpw); - - return NSERROR_OK; -} - - -/* Exported interface, documented in sslcert_viewer.h */ -nserror sslcert_viewer_accept(struct sslcert_session_data *ssl_d) -{ - assert(ssl_d != NULL); - - urldb_set_cert_permissions(ssl_d->url, true); - - ssl_d->cb(true, ssl_d->cbpw); - - return NSERROR_OK; -} - - -/* Exported interface, documented in sslcert_viewer.h */ -void -sslcert_viewer_redraw(struct sslcert_session_data *ssl_d, - int x, int y, - struct rect *clip, - const struct redraw_context *ctx) -{ - assert(ssl_d != NULL && - "sslcert_viewer_redraw() given bad session data"); - - treeview_redraw(ssl_d->tree, x, y, clip, ctx); -} - - -/* Exported interface, documented in sslcert_viewer.h */ -void -sslcert_viewer_mouse_action(struct sslcert_session_data *ssl_d, - browser_mouse_state mouse, - int x, int y) -{ - treeview_mouse_action(ssl_d->tree, mouse, x, y); -} - - -/* Exported interface, documented in sslcert_viewer.h */ -bool sslcert_viewer_keypress(struct sslcert_session_data *ssl_d, uint32_t key) -{ - return treeview_keypress(ssl_d->tree, key); -} diff --git a/desktop/sslcert_viewer.h b/desktop/sslcert_viewer.h deleted file mode 100644 index 9a57b965c..000000000 --- a/desktop/sslcert_viewer.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> - * Copyright 2013 Michael Drake <tlsa@netsurf-browser.org> - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/** - * \file - * SSL Certificate verification UI interface - */ - -#ifndef NETSURF_DESKTOP_SSLCERT_VIEWER_H -#define NETSURF_DESKTOP_SSLCERT_VIEWER_H - -#include "content/llcache.h" -#include "netsurf/mouse.h" - -struct sslcert_session_data; -struct redraw_context; -struct core_window_callback_table; -struct rect; - -/** - * Create ssl certificate viewer session data. - * - * \param num The number of certificates in the chain - * \param url Address of the page we're inspecting certificates of - * \param cb Low level cache callback - * \param cbpw Low level cache private data - * \param certs The SSL certificates - * \param ssl_d Updated to SSL certificate session data - * \return NSERROR_OK on success, appropriate error otherwise - * - * Pass the session data to sslcert_viewer_init. - * sslcert_viewer_fini destroys the session data. - */ -nserror sslcert_viewer_create_session_data( - unsigned long num, nsurl *url, llcache_query_response cb, - void *cbpw, const struct ssl_cert_info *certs, - struct sslcert_session_data **ssl_d); - - -/** - * Initialise a ssl certificate viewer from session data. - * - * This iterates through the certificates, building a treeview. - * - * \param cw_t Callback table for cert viewer's core_window - * \param core_window_handle The core_window in which the cert viewer is shown - * \param ssl_d SSL certificate session data - * \return NSERROR_OK on success, appropriate error otherwise - */ -nserror sslcert_viewer_init(struct core_window_callback_table *cw_t, - void *core_window_handle, struct sslcert_session_data *ssl_d); - - -/** - * Finalise a ssl certificate viewer. - * - * This destroys the certificate treeview and the certificate viewer module's - * session data. - * - * \param ssl_d SSL certificate session data - * \return NSERROR_OK on success, appropriate error otherwise - */ -nserror sslcert_viewer_fini(struct sslcert_session_data *ssl_d); - - -/** - * Reject a certificate chain. - * - * \param ssl_d SSL certificate session data - * \return NSERROR_OK on success, appropriate error otherwise - */ -nserror sslcert_viewer_reject(struct sslcert_session_data *ssl_d); - - -/** - * Accept a certificate chain. - * - * \param ssl_d SSL certificate session data - * \return NSERROR_OK on success, appropriate error otherwise - */ -nserror sslcert_viewer_accept(struct sslcert_session_data *ssl_d); - - -/** - * Redraw the ssl certificate viewer. - * - * \param ssl_d SSL certificate session data - * \param x X coordinate to render treeview at - * \param y Y coordinate to render treeview at - * \param clip Current clip rectangle (wrt tree origin) - * \param ctx Current redraw context - */ -void sslcert_viewer_redraw(struct sslcert_session_data *ssl_d, - int x, int y, struct rect *clip, - const struct redraw_context *ctx); - - -/** - * Handles all kinds of mouse action - * - * \param ssl_d SSL certificate session data - * \param mouse The current mouse state - * \param x X coordinate - * \param y Y coordinate - */ -void sslcert_viewer_mouse_action(struct sslcert_session_data *ssl_d, - browser_mouse_state mouse, int x, int y); - - -/** - * Key press handling. - * - * \param ssl_d SSL certificate session data - * \param key The ucs4 character codepoint - * \return true if the keypress is dealt with, false otherwise. - */ -bool sslcert_viewer_keypress(struct sslcert_session_data *ssl_d, uint32_t key); - -#endif diff --git a/desktop/textarea.c b/desktop/textarea.c index 149ca26b1..20f7d163b 100644 --- a/desktop/textarea.c +++ b/desktop/textarea.c @@ -45,9 +45,9 @@ #define TA_ALLOC_STEP 512 static plot_style_t pstyle_stroke_caret = { - .stroke_type = PLOT_OP_TYPE_SOLID, - .stroke_colour = CARET_COLOR, - .stroke_width = 1, + .stroke_type = PLOT_OP_TYPE_SOLID, + .stroke_colour = CARET_COLOR, + .stroke_width = plot_style_int_to_fixed(1), }; struct line_info { @@ -58,7 +58,7 @@ struct line_info { struct textarea_drag { textarea_drag_type type; union { - struct scrollbar* scrollbar; + struct scrollbar *scrollbar; } data; }; @@ -625,7 +625,7 @@ static bool textarea_select(struct textarea *ta, int b_start, int b_end, * \param ta Text area * \return True on success, false otherwise */ -static bool textarea_select_fragment(struct textarea * ta) +static bool textarea_select_fragment(struct textarea *ta) { int caret_pos; size_t sel_start, sel_end; @@ -676,7 +676,7 @@ static bool textarea_select_fragment(struct textarea * ta) * \param ta textarea widget * \return True on success, false otherwise */ -static bool textarea_select_paragraph(struct textarea * ta) +static bool textarea_select_paragraph(struct textarea *ta) { int caret_pos; size_t sel_start, sel_end; @@ -934,7 +934,7 @@ static bool textarea_reflow_multiline(struct textarea *ta, assert(ta->flags & TEXTAREA_MULTILINE); if (ta->lines == NULL) { - ta->lines = calloc(sizeof(struct line_info), LINE_CHUNK_SIZE); + ta->lines = calloc(LINE_CHUNK_SIZE, sizeof(struct line_info)); if (ta->lines == NULL) { NSLOG(netsurf, INFO, "Failed to allocate memory for textarea lines"); @@ -1607,7 +1607,7 @@ static bool textarea_copy_to_undo_buffer(struct textarea *ta, * \param b_end End byte index of replaced section (exclusive) * \param rep Replacement UTF-8 text to insert * \param rep_len Byte length of replacement UTF-8 text - * \param add_to_clipboard True iff replaced text to be added to clipboard + * \param add_to_clipboard True if replaced text to be added to clipboard * \param byte_delta Updated to change in byte count in textarea (ta->show) * \param r Updated to area where redraw is required * \return false on memory exhaustion, true otherwise @@ -1803,6 +1803,10 @@ static void textarea_setup_text_offsets(struct textarea *ta) { int text_y_offset, text_y_offset_baseline; + ta->line_height = FIXTOINT(FMUL(FLTTOFIX(1.3), FDIV(FMUL( + nscss_screen_dpi, FDIV(INTTOFIX(ta->fstyle.size), + INTTOFIX(PLOT_STYLE_SCALE))), F_72))); + text_y_offset = text_y_offset_baseline = ta->border_width; if (ta->flags & TEXTAREA_MULTILINE) { /* Multiline textarea */ @@ -1822,6 +1826,27 @@ static void textarea_setup_text_offsets(struct textarea *ta) } +/** + * Set font styles up for a textarea. + * + * \param[in] ta Textarea to update. + * \param[in] fstyle Font style to set in textarea. + * \param[in] selected_text Textarea selected text colour. + * \param[in] selected_bg Textarea selection background colour. + */ +static void textarea_set_text_style( + struct textarea *ta, + const plot_font_style_t *fstyle, + colour selected_text, + colour selected_bg) +{ + ta->fstyle = *fstyle; + + ta->sel_fstyle = *fstyle; + ta->sel_fstyle.foreground = selected_text; + ta->sel_fstyle.background = selected_bg; +} + /* exported interface, documented in textarea.h */ struct textarea *textarea_create(const textarea_flags flags, @@ -1861,11 +1886,10 @@ struct textarea *textarea_create(const textarea_flags flags, ret->border_width = setup->border_width; ret->border_col = setup->border_col; - ret->fstyle = setup->text; - - ret->sel_fstyle = setup->text; - ret->sel_fstyle.foreground = setup->selected_text; - ret->sel_fstyle.background = setup->selected_bg; + textarea_set_text_style(ret, + &setup->text, + setup->selected_text, + setup->selected_bg); ret->scroll_x = 0; ret->scroll_y = 0; @@ -1924,7 +1948,7 @@ struct textarea *textarea_create(const textarea_flags flags, ret->line_height = FIXTOINT(FMUL(FLTTOFIX(1.3), FDIV(FMUL( nscss_screen_dpi, FDIV(INTTOFIX(setup->text.size), - INTTOFIX(FONT_SIZE_SCALE))), F_72))); + INTTOFIX(PLOT_STYLE_SCALE))), F_72))); ret->caret_pos.line = ret->caret_pos.byte_off = -1; ret->caret_x = 0; @@ -2428,7 +2452,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) struct textarea_msg msg; struct rect r; /**< Redraw rectangle */ char utf8[6]; - unsigned int caret, length, b_off, b_len; + unsigned int caret, caret_copy, length, b_off, b_len; int h_extent = ta->h_extent; int v_extent = ta->v_extent; int line; @@ -2442,7 +2466,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) /* Word separators */ static const char *sep = " .\n"; - caret = textarea_get_caret(ta); + caret = caret_copy = textarea_get_caret(ta); line = ta->caret_pos.line; readonly = (ta->flags & TEXTAREA_READONLY ? true : false); @@ -2719,6 +2743,9 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) case NS_KEY_WORD_LEFT: if (readonly) break; + if (ta->sel_start != -1) { + textarea_clear_selection(ta); + } if (caret == 0) break; caret--; @@ -2732,13 +2759,59 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) break; } } + break; + case NS_KEY_DELETE_WORD_LEFT: + if (readonly) + break; + + /* If there is a selection, remove the selected + * characters */ if (ta->sel_start != -1) { + if (!textarea_replace_text(ta, ta->sel_start, + ta->sel_end, "", 0, false, + &byte_delta, &r)) + return false; + caret = ta->sel_start; textarea_clear_selection(ta); + redraw = true; + break; + } + + if (caret == 0) + break; + + /* caret goes left until a non-separator is + * encountered */ + caret--; + while (strchr(sep, ta->show->data[caret]) != NULL && + caret > 0) + caret--; + + /* caret goes left until a separator is encountered */ + for (; caret > 0; caret--) { + if (strchr(sep, ta->show->data[caret]) != + NULL) { + caret++; + break; + } } + + /* Remove the characters from new caret position to + * original caret position */ + if (!textarea_replace_text(ta, caret, caret_copy, + "", 0, false, &byte_delta, &r)) + return false; + + redraw = true; break; case NS_KEY_WORD_RIGHT: if (readonly) break; + if (ta->sel_start != -1) { + textarea_clear_selection(ta); + } + if (caret == ta->show->len - 1) + break; if (strchr(sep, ta->show->data[caret]) != NULL && caret < ta->show->len - 1) { while (strchr(sep, ta->show->data[caret]) != @@ -2755,9 +2828,49 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) while (strchr(sep, ta->show->data[caret]) != NULL && caret < ta->show->len - 1) caret++; + break; + case NS_KEY_DELETE_WORD_RIGHT: + if (readonly) + break; + + /* If there is a selection, remove the selected + * characters */ if (ta->sel_start != -1) { + if (!textarea_replace_text(ta, ta->sel_start, + ta->sel_end, "", 0, false, + &byte_delta, &r)) + return false; + caret = ta->sel_start; textarea_clear_selection(ta); + redraw = true; + break; } + + if (caret == ta->show->len - 1) + break; + + /* caret_copy goes right until a non-separator is + * encountered */ + while (strchr(sep, ta->show->data[caret_copy]) != NULL + && caret_copy < ta->show->len - 1) + caret_copy++; + + /* caret_copy goes right until a separator is + * encountered */ + for (; caret_copy < ta->show->len - 1; caret_copy++) { + if (strchr(sep, ta->show->data[caret_copy]) != + NULL) { + break; + } + } + + /* Remove all the characters from original caret + * position to caret_copy */ + if (!textarea_replace_text(ta, caret, caret_copy, + "", 0, false, &byte_delta, &r)) + return false; + + redraw = true; break; case NS_KEY_DELETE_LINE: if (readonly) @@ -3220,8 +3333,12 @@ void textarea_set_dimensions(struct textarea *ta, int width, int height) /* exported interface, documented in textarea.h */ -void textarea_set_layout(struct textarea *ta, int width, int height, - int top, int right, int bottom, int left) +void textarea_set_layout( + struct textarea *ta, + const plot_font_style_t *fstyle, + int width, int height, + int top, int right, + int bottom, int left) { struct rect r = {0, 0, 0, 0}; @@ -3232,6 +3349,10 @@ void textarea_set_layout(struct textarea *ta, int width, int height, ta->pad_bottom = bottom + ((ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH); ta->pad_left = left; + textarea_set_text_style(ta, fstyle, + ta->sel_fstyle.foreground, + ta->sel_fstyle.background); + textarea_setup_text_offsets(ta); if (ta->flags & TEXTAREA_MULTILINE) { diff --git a/desktop/textarea.h b/desktop/textarea.h index b386e50e8..82e0de95b 100644 --- a/desktop/textarea.h +++ b/desktop/textarea.h @@ -329,8 +329,12 @@ void textarea_set_dimensions(struct textarea *ta, int width, int height); * \param bottom the new bottom padding of the textarea * \param left the new left padding of the textarea */ -void textarea_set_layout(struct textarea *ta, int width, int height, - int top, int right, int bottom, int left); +void textarea_set_layout( + struct textarea *ta, + const plot_font_style_t *fstyle, + int width, int height, + int top, int right, + int bottom, int left); /** diff --git a/desktop/textinput.c b/desktop/textinput.c index c0e0ba8f7..b8dced689 100644 --- a/desktop/textinput.c +++ b/desktop/textinput.c @@ -33,13 +33,13 @@ #include "utils/talloc.h" #include "utils/utf8.h" #include "utils/utils.h" +#include "netsurf/types.h" #include "netsurf/mouse.h" #include "netsurf/form.h" #include "netsurf/window.h" +#include "netsurf/browser_window.h" #include "netsurf/keypress.h" -#include "render/box.h" -#include "render/html_internal.h" -#include "render/layout.h" +#include "content/content.h" #include "desktop/browser_private.h" #include "desktop/textinput.h" @@ -92,13 +92,14 @@ void browser_window_remove_caret(struct browser_window *bw, bool only_hide) root_bw = browser_window_get_root(bw); assert(root_bw != NULL); - if (only_hide) + if (only_hide) { root_bw->can_edit = true; - else + } else { root_bw->can_edit = false; + } if (root_bw->window) { - guit->window->remove_caret(root_bw->window); + guit->window->event(root_bw->window, GW_EVENT_REMOVE_CARET); } } diff --git a/desktop/treeview.c b/desktop/treeview.c index 1651ff5ef..107f490e0 100644 --- a/desktop/treeview.c +++ b/desktop/treeview.c @@ -22,13 +22,15 @@ * Treeview handling implementation. */ +#include "utils/config.h" + #include <string.h> #include "utils/utils.h" #include "utils/log.h" #include "utils/nsurl.h" +#include "utils/nscolour.h" #include "utils/nsoption.h" -#include "utils/config.h" #include "netsurf/bitmap.h" #include "netsurf/content.h" #include "netsurf/plotters.h" @@ -39,11 +41,13 @@ #include "content/hlcache.h" #include "css/utils.h" -#include "desktop/system_colour.h" +#include "desktop/bitmap.h" #include "desktop/knockout.h" #include "desktop/textarea.h" #include "desktop/treeview.h" +#include "desktop/cw_helper.h" #include "desktop/gui_internal.h" +#include "desktop/system_colour.h" /** * The maximum horizontal size a treeview can possibly be. @@ -244,7 +248,6 @@ struct treeview { const struct treeview_callback_table *callbacks; /**< For node events */ - const struct core_window_callback_table *cw_t; /**< Window cb table */ struct core_window *cw_h; /**< Core window handle */ }; @@ -333,7 +336,7 @@ static struct treeview_resource treeview_res[TREE_RES_LAST] = { * \param[in] tree Treeview to get the height of. * \return the display height in pixels. */ -static inline int treeview__get_display_height(treeview *tree) +static inline int treeview__get_display_height(const treeview *tree) { return (tree->search.search == false) ? tree->root->height : @@ -351,13 +354,47 @@ static inline void treeview__cw_invalidate_area( const struct treeview *tree, const struct rect *r) { - if (tree->cw_t != NULL) { - tree->cw_t->invalidate(tree->cw_h, r); + if (tree->cw_h != NULL) { + guit->corewindow->invalidate(tree->cw_h, r); } } /** + * Corewindow callback wrapper: Request a full redraw of the window + * + * \param[in] tree The treeview to request redraw on. + */ +static inline void treeview__cw_full_redraw( + const struct treeview *tree) +{ + if (tree->cw_h != NULL) { + static const struct rect r = { + .x0 = 0, + .y0 = 0, + .x1 = REDRAW_MAX, + .y1 = REDRAW_MAX, + }; + guit->corewindow->invalidate(tree->cw_h, &r); + } +} + + +/** + * Get height used by treeview's search bar (or 0 if not present). + * + * \param tree Treeview object to check. + * \return height used by search bar in pixels. + */ +static inline unsigned treeview__get_search_height( + const treeview *tree) +{ + return (tree->flags & TREEVIEW_SEARCHABLE) ? + tree_g.line_height : 0; +} + + +/** * Corewindow callback wrapper: Update the limits of the window * * \param[in] tree The treeview to update size for. @@ -368,12 +405,10 @@ static inline void treeview__cw_update_size( const struct treeview *tree, int width, int height) { - int search_height = (tree->flags & TREEVIEW_SEARCHABLE) ? - tree_g.line_height : 0; - - if (tree->cw_t != NULL) { - tree->cw_t->update_size(tree->cw_h, width, - height + search_height); + if (tree->cw_h != NULL) { + guit->corewindow->set_extent(tree->cw_h, + width, + height + treeview__get_search_height(tree)); } } @@ -393,12 +428,9 @@ static inline void treeview__cw_scroll_top( .y1 = tree_g.line_height, }; - if (tree->cw_t != NULL) { - tree->cw_t->scroll_visible(tree->cw_h, &r); - } + cw_helper_scroll_visible(tree->cw_h, &r); } - /** * Corewindow callback wrapper: Get window viewport dimensions * @@ -410,8 +442,8 @@ static inline void treeview__cw_get_window_dimensions( const struct treeview *tree, int *width, int *height) { - if (tree->cw_t != NULL) { - tree->cw_t->get_window_dimensions(tree->cw_h, width, height); + if (tree->cw_h != NULL) { + guit->corewindow->get_dimensions(tree->cw_h, width, height); } } @@ -426,8 +458,8 @@ static inline void treeview__cw_drag_status( const struct treeview *tree, core_window_drag_status ds) { - if (tree->cw_t != NULL) { - tree->cw_t->drag_status(tree->cw_h, ds); + if (tree->cw_h != NULL) { + guit->corewindow->drag_status(tree->cw_h, ds); } } @@ -501,9 +533,8 @@ static inline treeview_node * treeview_node_next(treeview_node *node, bool full) */ static treeview_node * treeview_y_node(treeview *tree, int target_y) { + int y = treeview__get_search_height(tree); treeview_node *n; - int y = 0; - int h; assert(tree != NULL); assert(tree->root != NULL); @@ -511,7 +542,7 @@ static treeview_node * treeview_y_node(treeview *tree, int target_y) n = treeview_node_next(tree->root, false); while (n != NULL) { - h = (n->type == TREE_NODE_ENTRY) ? + int h = (n->type == TREE_NODE_ENTRY) ? n->height : tree_g.line_height; if (target_y >= y && target_y < y + h) return n; @@ -531,10 +562,12 @@ static treeview_node * treeview_y_node(treeview *tree, int target_y) * \param node Node to get position of * \return node's y position */ -static int treeview_node_y(treeview *tree, treeview_node *node) +static int treeview_node_y( + const treeview *tree, + const treeview_node *node) { treeview_node *n; - int y = 0; + int y = treeview__get_search_height(tree); assert(tree != NULL); assert(tree->root != NULL); @@ -553,6 +586,54 @@ static int treeview_node_y(treeview *tree, treeview_node *node) /** + * Corewindow callback_wrapper: Scroll to make node visible + * + * \param[in] tree The treeview to scroll. + * \param[in] node The treeview node to scroll to visibility. + */ +static inline void treeview__cw_scroll_to_node( + const struct treeview *tree, + const struct treeview_node *node) +{ + struct rect r = { + .x0 = 0, + .y0 = treeview_node_y(tree, node), + .x1 = 1, + .y1 = ((node->type == TREE_NODE_ENTRY) ? + node->height : tree_g.line_height), + }; + + r.y1 += r.y0; /* Apply the Y offset to the second Y coordinate */ + + cw_helper_scroll_visible(tree->cw_h, &r); +} + + +/** + * Redraw tree from given node to the bottom. + * + * \param[in] tree Tree to redraw from node in. + * \param[in] node Node to redraw from. + */ +static void treeview__redraw_from_node( + const treeview *tree, + const treeview_node *node) +{ + struct rect r = { + .x0 = 0, + .y0 = treeview_node_y(tree, node), + .x1 = REDRAW_MAX, + .y1 = treeview__get_display_height(tree) + + treeview__get_search_height(tree), + }; + + assert(tree != NULL); + + treeview__cw_invalidate_area(tree, &r); +} + + +/** * The treeview walk mode. Controls which nodes are visited in a walk. */ enum treeview_walk_mode { @@ -790,8 +871,7 @@ static nserror treeview__search( nserror err; uint32_t height; uint32_t prev_height = treeview__get_display_height(tree); - int search_height = (tree->flags & TREEVIEW_SEARCHABLE) ? - tree_g.line_height : 0; + int search_height = treeview__get_search_height(tree); struct treeview_search_walk_data sw = { .len = len, .text = text, @@ -855,6 +935,12 @@ static void treeview__search_cancel(treeview *tree, bool drop_focus) return; } + if (textarea_get_text(tree->search.textarea, NULL, 0) == 1) { + // If there's no text in the search box, we drop focus on a + // cancel. Note '1' because it includes the trailing \0 + drop_focus = true; + } + if (drop_focus) { tree->search.active = false; textarea_set_caret(tree->search.textarea, -1); @@ -866,6 +952,34 @@ static void treeview__search_cancel(treeview *tree, bool drop_focus) treeview__cw_invalidate_area(tree, &r); } +/** + * Convert from treeview drag to core window drag type. + * + * \param[in] tree A treeview. + * \return Core window drag type. + */ +static core_window_drag_status treeview__get_cw_drag_type( + const treeview *tree) +{ + assert(tree != NULL); + + switch (tree->drag.type) { + case TV_DRAG_NONE: + return CORE_WINDOW_DRAG_NONE; + + case TV_DRAG_SELECTION: + return CORE_WINDOW_DRAG_SELECTION; + + case TV_DRAG_TEXTAREA: /* Fall through.*/ + case TV_DRAG_SEARCH: + return CORE_WINDOW_DRAG_TEXT_SELECTION; + + case TV_DRAG_MOVE: + return CORE_WINDOW_DRAG_MOVE; + } + + return CORE_WINDOW_DRAG_NONE; +} /** * Callback for textarea_create, in desktop/treeview.h @@ -892,7 +1006,8 @@ static void treeview_textarea_search_callback(void *data, /* Textarea drag started */ tree->drag.type = TV_DRAG_SEARCH; } - treeview__cw_drag_status(tree, tree->drag.type); + treeview__cw_drag_status(tree, + treeview__get_cw_drag_type(tree)); break; case TEXTAREA_MSG_REDRAW_REQUEST: @@ -1904,18 +2019,17 @@ static struct textarea *treeview__create_textarea( /* Exported interface, documented in treeview.h */ nserror -treeview_create(treeview **tree, +treeview_create(treeview **treeout, const struct treeview_callback_table *callbacks, int n_fields, struct treeview_field_desc fields[], - const struct core_window_callback_table *cw_t, struct core_window *cw, treeview_flags flags) { + treeview *tree; nserror error; - int i; + int fldidx; - assert((cw_t == NULL && cw == NULL) || (cw_t != NULL && cw != NULL)); assert(callbacks != NULL); assert(fields != NULL); @@ -1923,86 +2037,91 @@ treeview_create(treeview **tree, assert(fields[n_fields - 1].flags & TREE_FLAG_DEFAULT); assert(n_fields >= 2); - *tree = malloc(sizeof(struct treeview)); - if (*tree == NULL) { + tree = malloc(sizeof(struct treeview)); + if (tree == NULL) { return NSERROR_NOMEM; } - (*tree)->fields = malloc(sizeof(struct treeview_field) * n_fields); - if ((*tree)->fields == NULL) { + tree->fields = malloc(sizeof(struct treeview_field) * n_fields); + if (tree->fields == NULL) { free(tree); return NSERROR_NOMEM; } - error = treeview_create_node_root(&((*tree)->root)); + error = treeview_create_node_root(&(tree->root)); if (error != NSERROR_OK) { - free((*tree)->fields); - free(*tree); + free(tree->fields); + free(tree); return error; } - (*tree)->field_width = 0; - for (i = 0; i < n_fields; i++) { - struct treeview_field *f = &((*tree)->fields[i]); + tree->field_width = 0; + for (fldidx = 0; fldidx < n_fields; fldidx++) { + struct treeview_field *f = &(tree->fields[fldidx]); - f->flags = fields[i].flags; - f->field = lwc_string_ref(fields[i].field); - f->value.data = lwc_string_data(fields[i].field); - f->value.len = lwc_string_length(fields[i].field); + f->flags = fields[fldidx].flags; + f->field = lwc_string_ref(fields[fldidx].field); + f->value.data = lwc_string_data(fields[fldidx].field); + f->value.len = lwc_string_length(fields[fldidx].field); - guit->layout->width(&plot_style_odd.text, f->value.data, - f->value.len, &(f->value.width)); + guit->layout->width(&plot_style_odd.text, + f->value.data, + f->value.len, + &(f->value.width)); - if (f->flags & TREE_FLAG_SHOW_NAME) - if ((*tree)->field_width < f->value.width) - (*tree)->field_width = f->value.width; + if (f->flags & TREE_FLAG_SHOW_NAME) { + if (tree->field_width < f->value.width) { + tree->field_width = f->value.width; + } + } } - (*tree)->field_width += tree_g.step_width; + tree->field_width += tree_g.step_width; - (*tree)->callbacks = callbacks; - (*tree)->n_fields = n_fields - 1; + tree->callbacks = callbacks; + tree->n_fields = n_fields - 1; - (*tree)->drag.type = TV_DRAG_NONE; - (*tree)->drag.start_node = NULL; - (*tree)->drag.start.x = 0; - (*tree)->drag.start.y = 0; - (*tree)->drag.start.node_y = 0; - (*tree)->drag.start.node_h = 0; - (*tree)->drag.prev.x = 0; - (*tree)->drag.prev.y = 0; - (*tree)->drag.prev.node_y = 0; - (*tree)->drag.prev.node_h = 0; + tree->drag.type = TV_DRAG_NONE; + tree->drag.start_node = NULL; + tree->drag.start.x = 0; + tree->drag.start.y = 0; + tree->drag.start.node_y = 0; + tree->drag.start.node_h = 0; + tree->drag.prev.x = 0; + tree->drag.prev.y = 0; + tree->drag.prev.node_y = 0; + tree->drag.prev.node_h = 0; - (*tree)->move.root = NULL; - (*tree)->move.target = NULL; - (*tree)->move.target_pos = TV_TARGET_NONE; + tree->move.root = NULL; + tree->move.target = NULL; + tree->move.target_pos = TV_TARGET_NONE; - (*tree)->edit.textarea = NULL; - (*tree)->edit.node = NULL; + tree->edit.textarea = NULL; + tree->edit.node = NULL; if (flags & TREEVIEW_SEARCHABLE) { - (*tree)->search.textarea = treeview__create_textarea( - *tree, 600, tree_g.line_height, - plot_style_even.text.background, - plot_style_even.text.background, - plot_style_even.text.foreground, + tree->search.textarea = treeview__create_textarea( + tree, 600, tree_g.line_height, + nscolours[NSCOLOUR_TEXT_INPUT_BG], + nscolours[NSCOLOUR_TEXT_INPUT_BG], + nscolours[NSCOLOUR_TEXT_INPUT_FG], plot_style_odd.text, treeview_textarea_search_callback); - if ((*tree)->search.textarea == NULL) { - treeview_destroy(*tree); + if (tree->search.textarea == NULL) { + treeview_destroy(tree); return NSERROR_NOMEM; } } else { - (*tree)->search.textarea = NULL; + tree->search.textarea = NULL; } - (*tree)->search.active = false; - (*tree)->search.search = false; + tree->search.active = false; + tree->search.search = false; - (*tree)->flags = flags; + tree->flags = flags; - (*tree)->cw_t = cw_t; - (*tree)->cw_h = cw; + tree->cw_h = cw; + + *treeout = tree; return NSERROR_OK; } @@ -2010,18 +2129,14 @@ treeview_create(treeview **tree, /* Exported interface, documented in treeview.h */ nserror -treeview_cw_attach(treeview *tree, - const struct core_window_callback_table *cw_t, - struct core_window *cw) +treeview_cw_attach(treeview *tree, struct core_window *cw) { - assert(cw_t != NULL); assert(cw != NULL); - if (tree->cw_t != NULL || tree->cw_h != NULL) { + if (tree->cw_h != NULL) { NSLOG(netsurf, INFO, "Treeview already attached."); return NSERROR_UNKNOWN; } - tree->cw_t = cw_t; tree->cw_h = cw; return NSERROR_OK; @@ -2031,7 +2146,6 @@ treeview_cw_attach(treeview *tree, /* Exported interface, documented in treeview.h */ nserror treeview_cw_detach(treeview *tree) { - tree->cw_t = NULL; tree->cw_h = NULL; treeview__search_cancel(tree, true); @@ -2083,7 +2197,8 @@ treeview_node_expand_internal(treeview *tree, treeview_node *node) { treeview_node *child; struct treeview_node_entry *e; - int additional_height = 0; + int additional_height_folders = 0; + int additional_height_entries = 0; int i; assert(tree != NULL); @@ -2111,7 +2226,7 @@ treeview_node_expand_internal(treeview *tree, treeview_node *node) &(child->text.width)); } - additional_height += child->height; + additional_height_folders += child->height; child = child->next_sib; } while (child != NULL); @@ -2133,7 +2248,7 @@ treeview_node_expand_internal(treeview *tree, treeview_node *node) } /* Add height for field */ - additional_height += tree_g.line_height; + additional_height_entries += tree_g.line_height; } break; @@ -2152,17 +2267,18 @@ treeview_node_expand_internal(treeview *tree, treeview_node *node) for (struct treeview_node *n = node; (n != NULL) && (n->flags & TV_NFLAGS_EXPANDED); n = n->parent) { - n->height += additional_height; + n->height += additional_height_entries + + additional_height_folders; } if (tree->search.search && node->type == TREE_NODE_ENTRY && node->flags & TV_NFLAGS_MATCHED) { - tree->search.height += additional_height; + tree->search.height += additional_height_entries; } /* Inform front end of change in dimensions */ - if (additional_height != 0) { + if (additional_height_entries + additional_height_folders != 0) { treeview__cw_update_size(tree, -1, treeview__get_display_height(tree)); } @@ -2175,17 +2291,13 @@ treeview_node_expand_internal(treeview *tree, treeview_node *node) nserror treeview_node_expand(treeview *tree, treeview_node *node) { nserror res; - struct rect r; res = treeview_node_expand_internal(tree, node); + NSLOG(netsurf, INFO, "Expanding!"); if (res == NSERROR_OK) { /* expansion was successful, attempt redraw */ - r.x0 = 0; - r.y0 = treeview_node_y(tree, node); - r.x1 = REDRAW_MAX; - r.y1 = treeview__get_display_height(tree); - - treeview__cw_invalidate_area(tree, &r); + treeview__redraw_from_node(tree, node); + NSLOG(netsurf, INFO, "Expanded!"); } return res; @@ -2212,7 +2324,8 @@ struct treeview_contract_data { static nserror treeview_node_contract_cb(treeview_node *n, void *ctx, bool *end) { struct treeview_contract_data *data = ctx; - int h_reduction; + int h_reduction_folder = 0; + int h_reduction_entry = 0; assert(n != NULL); assert(n->type != TREE_NODE_ROOT); @@ -2225,17 +2338,30 @@ static nserror treeview_node_contract_cb(treeview_node *n, void *ctx, bool *end) return NSERROR_OK; } - h_reduction = n->height - tree_g.line_height; - assert(h_reduction >= 0); + switch (n->type) { + case TREE_NODE_FOLDER: + h_reduction_folder = n->height - tree_g.line_height; + break; + + case TREE_NODE_ENTRY: + h_reduction_entry = n->height - tree_g.line_height; + break; + + default: + break; + } + + + assert(h_reduction_folder + h_reduction_entry >= 0); for (struct treeview_node *node = n; (node != NULL) && (node->flags & TV_NFLAGS_EXPANDED); node = node->parent) { - node->height -= h_reduction; + node->height -= h_reduction_folder + h_reduction_entry; } if (data->tree->search.search) { - data->tree->search.height -= h_reduction; + data->tree->search.height -= h_reduction_entry; } n->flags ^= TV_NFLAGS_EXPANDED; @@ -2289,19 +2415,15 @@ treeview_node_contract_internal(treeview *tree, treeview_node *node) nserror treeview_node_contract(treeview *tree, treeview_node *node) { nserror res; - struct rect r; assert(tree != NULL); res = treeview_node_contract_internal(tree, node); + NSLOG(netsurf, INFO, "Contracting!"); if (res == NSERROR_OK) { /* successful contraction, request redraw */ - r.x0 = 0; - r.y0 = treeview_node_y(tree, node); - r.x1 = REDRAW_MAX; - r.y1 = tree->root->height; - - treeview__cw_invalidate_area(tree, &r); + treeview__redraw_from_node(tree, node); + NSLOG(netsurf, INFO, "Contracted!"); } return res; @@ -2311,6 +2433,7 @@ nserror treeview_node_contract(treeview *tree, treeview_node *node) /* Exported interface, documented in treeview.h */ nserror treeview_contract(treeview *tree, bool all) { + int search_height = treeview__get_search_height(tree); struct treeview_contract_data data; bool selected; treeview_node *n; @@ -2322,7 +2445,7 @@ nserror treeview_contract(treeview *tree, bool all) r.x0 = 0; r.y0 = 0; r.x1 = REDRAW_MAX; - r.y1 = tree->root->height; + r.y1 = tree->root->height + search_height; data.tree = tree; data.only_entries = !all; @@ -2444,7 +2567,7 @@ static void treeview_redraw_tree( const int x, const int y, int *render_y_in_out, - struct rect *r, + const struct rect *r, struct content_redraw_data *data, const struct redraw_context *ctx) { @@ -2663,7 +2786,7 @@ static void treeview_redraw_search( const int x, const int y, int *render_y_in_out, - struct rect *r, + const struct rect *r, struct content_redraw_data *data, const struct redraw_context *ctx) { @@ -3010,6 +3133,7 @@ struct treeview_selection_walk_data { } drag; struct { treeview_node *prev; + treeview_node *fixed; } yank; struct { treeview_node *n; @@ -3112,6 +3236,10 @@ treeview_node_selection_walk_cb(treeview_node *n, treeview_node *p = n->parent; int h = 0; + if (n == sw->data.yank.fixed) { + break; + } + if (treeview_unlink_node(n)) h = n->height; @@ -3270,8 +3398,7 @@ static bool treeview_clear_selection(treeview *tree, struct rect *rect) sw.purpose = TREEVIEW_WALK_CLEAR_SELECTION; sw.data.redraw.required = false; sw.data.redraw.rect = rect; - sw.current_y = (tree->flags & TREEVIEW_SEARCHABLE) ? - tree_g.line_height : 0; + sw.current_y = treeview__get_search_height(tree); treeview_walk_internal(tree, tree->root, TREEVIEW_WALK_MODE_DISPLAY, NULL, @@ -3300,8 +3427,7 @@ static bool treeview_select_all(treeview *tree, struct rect *rect) sw.purpose = TREEVIEW_WALK_SELECT_ALL; sw.data.redraw.required = false; sw.data.redraw.rect = rect; - sw.current_y = (tree->flags & TREEVIEW_SEARCHABLE) ? - tree_g.line_height : 0; + sw.current_y = treeview__get_search_height(tree); treeview_walk_internal(tree, tree->root, TREEVIEW_WALK_MODE_DISPLAY, NULL, @@ -3321,8 +3447,7 @@ static void treeview_commit_selection_drag(treeview *tree) struct treeview_selection_walk_data sw; sw.purpose = TREEVIEW_WALK_COMMIT_SELECT_DRAG; - sw.current_y = (tree->flags & TREEVIEW_SEARCHABLE) ? - tree_g.line_height : 0; + sw.current_y = treeview__get_search_height(tree); if (tree->drag.start.y > tree->drag.prev.y) { sw.data.drag.sel_min = tree->drag.prev.y; @@ -3341,13 +3466,15 @@ static void treeview_commit_selection_drag(treeview *tree) /** * Yank a selection to the node move list. * - * \param tree Treeview object to yank selection from + * \param tree Treeview object to yank selection from + * \param fixed Treeview node that should not be yanked */ -static void treeview_move_yank_selection(treeview *tree) +static void treeview_move_yank_selection(treeview *tree, treeview_node *fixed) { struct treeview_selection_walk_data sw; sw.purpose = TREEVIEW_WALK_YANK_SELECTION; + sw.data.yank.fixed = fixed; sw.data.yank.prev = NULL; sw.tree = tree; @@ -3404,12 +3531,12 @@ static bool treeview_delete_selection(treeview *tree, struct rect *rect) rect->x0 = 0; rect->y0 = 0; rect->x1 = REDRAW_MAX; - rect->y1 = tree->root->height; + rect->y1 = treeview__get_display_height(tree); sw.purpose = TREEVIEW_WALK_DELETE_SELECTION; sw.data.redraw.required = false; sw.data.redraw.rect = rect; - sw.current_y = 0; + sw.current_y = treeview__get_search_height(tree); sw.tree = tree; treeview_walk_internal(tree, tree->root, @@ -3442,7 +3569,7 @@ static bool treeview_propagate_selection(treeview *tree, struct rect *rect) sw.purpose = TREEVIEW_WALK_PROPAGATE_SELECTION; sw.data.redraw.required = false; sw.data.redraw.rect = rect; - sw.current_y = 0; + sw.current_y = treeview__get_search_height(tree); sw.tree = tree; treeview_walk_internal(tree, tree->root, @@ -3521,16 +3648,17 @@ static nserror treeview_move_selection(treeview *tree, struct rect *rect) parent = relation->parent; } - /* The node that we're moving selection to can't itself be selected */ - assert(!(relation->flags & TV_NFLAGS_SELECTED)); - /* Move all selected nodes from treeview to tree->move.root */ - treeview_move_yank_selection(tree); + treeview_move_yank_selection(tree, relation); /* Move all nodes on tree->move.root to target location */ for (node = tree->move.root; node != NULL; node = next) { next = node->next_sib; + if (node == relation) { + continue; + } + if (!(parent->flags & TV_NFLAGS_EXPANDED)) { if (node->flags & TV_NFLAGS_EXPANDED) treeview_node_contract_internal(tree, node); @@ -3769,8 +3897,10 @@ treeview_keyboard_navigation(treeview *tree, uint32_t key, struct rect *rect) .n_selected = 0, .prev_n_selected = 0 }; - int h = tree->root->height; + int search_height = treeview__get_search_height(tree); + int h = treeview__get_display_height(tree) + search_height; bool redraw = false; + struct treeview_node *scroll_to_node = NULL; /* Fill out the nav. state struct, by examining the current selection * state */ @@ -3778,6 +3908,8 @@ treeview_keyboard_navigation(treeview *tree, uint32_t key, struct rect *rect) TREEVIEW_WALK_MODE_DISPLAY, NULL, treeview_node_nav_cb, &ns); + scroll_to_node = ns.curr; + if (tree->search.search == false) { if (ns.next == NULL) ns.next = tree->root->children; @@ -3798,10 +3930,12 @@ treeview_keyboard_navigation(treeview *tree, uint32_t key, struct rect *rect) ns.curr->parent->type != TREE_NODE_ROOT) { /* Step to parent */ ns.curr->parent->flags |= TV_NFLAGS_SELECTED; + scroll_to_node = ns.curr->parent; } else if (ns.curr != NULL && tree->root->children != NULL) { /* Select first node in tree */ tree->root->children->flags |= TV_NFLAGS_SELECTED; + scroll_to_node = tree->root->children; } break; @@ -3814,6 +3948,7 @@ treeview_keyboard_navigation(treeview *tree, uint32_t key, struct rect *rect) /* Step to first child */ ns.curr->children->flags |= TV_NFLAGS_SELECTED; + scroll_to_node = ns.curr->children; } else { /* Retain current node selection */ ns.curr->flags |= TV_NFLAGS_SELECTED; @@ -3835,6 +3970,7 @@ treeview_keyboard_navigation(treeview *tree, uint32_t key, struct rect *rect) if (ns.prev != NULL) { /* Step to previous node */ ns.prev->flags |= TV_NFLAGS_SELECTED; + scroll_to_node = ns.prev; } break; @@ -3842,6 +3978,7 @@ treeview_keyboard_navigation(treeview *tree, uint32_t key, struct rect *rect) if (ns.next != NULL) { /* Step to next node */ ns.next->flags |= TV_NFLAGS_SELECTED; + scroll_to_node = ns.next; } break; @@ -3849,12 +3986,14 @@ treeview_keyboard_navigation(treeview *tree, uint32_t key, struct rect *rect) break; } + treeview__cw_scroll_to_node(tree, scroll_to_node); + /* TODO: Deal with redraw area properly */ rect->x0 = 0; rect->y0 = 0; rect->x1 = REDRAW_MAX; - if (tree->root->height > h) - rect->y1 = tree->root->height; + if (treeview__get_display_height(tree) + search_height > h) + rect->y1 = treeview__get_display_height(tree) + search_height; else rect->y1 = h; redraw = true; @@ -4106,7 +4245,8 @@ static void treeview_textarea_callback(void *data, struct textarea_msg *msg) /* Textarea drag started */ tree->drag.type = TV_DRAG_TEXTAREA; } - treeview__cw_drag_status(tree, tree->drag.type); + treeview__cw_drag_status(tree, + treeview__get_cw_drag_type(tree)); break; case TEXTAREA_MSG_REDRAW_REQUEST: @@ -4290,6 +4430,7 @@ struct treeview_mouse_action { int x; int y; int current_y; /* Y coordinate value of top of current node */ + int search_height; }; @@ -4477,7 +4618,8 @@ treeview_node_mouse_action_cb(treeview_node *node, if (((node->type == TREE_NODE_FOLDER) && (ma->mouse & BROWSER_MOUSE_DOUBLE_CLICK) && click) || (part == TV_NODE_PART_TOGGLE && click)) { - int h = ma->tree->root->height; + int h = treeview__get_display_height(ma->tree) + + ma->search_height; /* Clear any existing selection */ redraw |= treeview_clear_selection(ma->tree, &r); @@ -4495,7 +4637,13 @@ treeview_node_mouse_action_cb(treeview_node *node, /* Set up redraw */ if (!redraw || r.y0 > ma->current_y) r.y0 = ma->current_y; - r.y1 = h > ma->tree->root->height ? h : ma->tree->root->height; + if (h > treeview__get_display_height(ma->tree) + + ma->search_height) { + r.y1 = h; + } else { + r.y1 = treeview__get_display_height(ma->tree) + + ma->search_height; + } redraw = true; } else if ((node->type == TREE_NODE_ENTRY) && @@ -4576,8 +4724,7 @@ treeview_mouse_action(treeview *tree, browser_mouse_state mouse, int x, int y) { struct rect r; bool redraw = false; - int search_height = (tree->flags & TREEVIEW_SEARCHABLE) ? - tree_g.line_height : 0; + int search_height = treeview__get_search_height(tree); assert(tree != NULL); assert(tree->root != NULL); @@ -4592,9 +4739,7 @@ treeview_mouse_action(treeview *tree, browser_mouse_state mouse, int x, int y) textarea_mouse_action(tree->edit.textarea, mouse, x - tree->edit.x, y - tree->edit.y); return; - } else if (tree->drag.type == TV_DRAG_SEARCH || - (y < search_height && - tree->drag.type == TV_DRAG_NONE)) { + } else if (tree->drag.type == TV_DRAG_SEARCH) { if (tree->search.active == false) { tree->search.active = true; if (treeview_clear_selection(tree, &r)) { @@ -4606,6 +4751,16 @@ treeview_mouse_action(treeview *tree, browser_mouse_state mouse, int x, int y) y); return; } else if (mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2) && + y < search_height && tree->search.active == false) { + tree->search.active = true; + if (treeview_clear_selection(tree, &r)) { + treeview__cw_invalidate_area(tree, &r); + } + textarea_mouse_action(tree->search.textarea, mouse, + x - tree_g.window_padding - tree_g.icon_size, + y); + return; + } else if (mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2) && tree->search.active == true) { tree->search.active = false; @@ -4734,13 +4889,14 @@ treeview_mouse_action(treeview *tree, browser_mouse_state mouse, int x, int y) } else { /* On tree */ - struct treeview_mouse_action ma; - - ma.tree = tree; - ma.mouse = mouse; - ma.x = x; - ma.y = y; - ma.current_y = search_height; + struct treeview_mouse_action ma = { + .tree = tree, + .mouse = mouse, + .x = x, + .y = y, + .current_y = search_height, + .search_height = search_height, + }; treeview_walk_internal(tree, tree->root, TREEVIEW_WALK_MODE_DISPLAY, NULL, @@ -4748,12 +4904,10 @@ treeview_mouse_action(treeview *tree, browser_mouse_state mouse, int x, int y) } } - /* Exported interface, documented in treeview.h */ int treeview_get_height(treeview *tree) { - int search_height = (tree->flags & TREEVIEW_SEARCHABLE) ? - tree_g.line_height : 0; + int search_height = treeview__get_search_height(tree); int height = treeview__get_display_height(tree); assert(tree != NULL); @@ -4764,6 +4918,31 @@ int treeview_get_height(treeview *tree) return height + search_height; } +/* Exported interface, documented in treeview.h */ +nserror treeview_set_search_string( + treeview *tree, + const char *string) +{ + if (!(tree->flags & TREEVIEW_SEARCHABLE)) { + return NSERROR_BAD_PARAMETER; + } + + if (string == NULL || strlen(string) == 0) { + tree->search.active = false; + tree->search.search = false; + return treeview__search(tree, "", 0); + } + + tree->search.active = true; + tree->search.search = true; + if (!textarea_set_text(tree->search.textarea, string)) { + return NSERROR_UNKNOWN; + } + + treeview__cw_full_redraw(tree); + + return NSERROR_OK; +} /** * Initialise the plot styles from CSS system colour values. @@ -4773,75 +4952,45 @@ int treeview_get_height(treeview *tree) */ static nserror treeview_init_plot_styles(int font_pt_size) { - nserror res; - /* Background colour */ plot_style_even.bg.stroke_type = PLOT_OP_TYPE_NONE; plot_style_even.bg.stroke_width = 0; plot_style_even.bg.stroke_colour = 0; plot_style_even.bg.fill_type = PLOT_OP_TYPE_SOLID; - res = ns_system_colour_char("Window", &plot_style_even.bg.fill_colour); - if (res != NSERROR_OK) { - return res; - } + plot_style_even.bg.fill_colour = nscolours[NSCOLOUR_WIN_EVEN_BG]; /* Text colour */ plot_style_even.text.family = PLOT_FONT_FAMILY_SANS_SERIF; plot_style_even.text.size = font_pt_size; plot_style_even.text.weight = 400; plot_style_even.text.flags = FONTF_NONE; - res = ns_system_colour_char("WindowText", &plot_style_even.text.foreground); - if (res != NSERROR_OK) { - return res; - } - res = ns_system_colour_char("Window", &plot_style_even.text.background); - if (res != NSERROR_OK) { - return res; - } + plot_style_even.text.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG]; + plot_style_even.text.background = nscolours[NSCOLOUR_WIN_EVEN_BG]; /* Entry field text colour */ plot_style_even.itext = plot_style_even.text; - plot_style_even.itext.foreground = mix_colour( - plot_style_even.text.foreground, - plot_style_even.text.background, - 255 * 10 / 16); + plot_style_even.itext.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_FADED]; /* Selected background colour */ plot_style_even.sbg = plot_style_even.bg; - res = ns_system_colour_char("Highlight", &plot_style_even.sbg.fill_colour); - if (res != NSERROR_OK) { - return res; - } + plot_style_even.sbg.fill_colour = nscolours[NSCOLOUR_SEL_BG]; /* Selected text colour */ plot_style_even.stext = plot_style_even.text; - res = ns_system_colour_char("HighlightText", &plot_style_even.stext.foreground); - if (res != NSERROR_OK) { - return res; - } - res = ns_system_colour_char("Highlight", &plot_style_even.stext.background); - if (res != NSERROR_OK) { - return res; - } + plot_style_even.stext.foreground = nscolours[NSCOLOUR_SEL_FG]; + plot_style_even.stext.background = nscolours[NSCOLOUR_SEL_BG]; /* Selected entry field text colour */ plot_style_even.sitext = plot_style_even.stext; - plot_style_even.sitext.foreground = mix_colour( - plot_style_even.stext.foreground, - plot_style_even.stext.background, - 255 * 25 / 32); + plot_style_even.sitext.foreground = nscolours[NSCOLOUR_SEL_FG_SUBTLE]; /* Odd numbered node styles */ plot_style_odd.bg = plot_style_even.bg; - plot_style_odd.bg.fill_colour = mix_colour( - plot_style_even.bg.fill_colour, - plot_style_even.text.foreground, 255 * 15 / 16); + plot_style_odd.bg.fill_colour = nscolours[NSCOLOUR_WIN_ODD_BG]; plot_style_odd.text = plot_style_even.text; plot_style_odd.text.background = plot_style_odd.bg.fill_colour; plot_style_odd.itext = plot_style_odd.text; - plot_style_odd.itext.foreground = mix_colour( - plot_style_odd.text.foreground, - plot_style_odd.text.background, 255 * 10 / 16); + plot_style_odd.itext.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_FADED]; plot_style_odd.sbg = plot_style_even.sbg; plot_style_odd.stext = plot_style_even.stext; @@ -4927,7 +5076,7 @@ treeview_generate_triangle_bitmap(colour bg, colour fg, int size) colour colour4 = fg; /* Create the bitmap */ - b = guit->bitmap->create(size, size, BITMAP_NEW | BITMAP_OPAQUE); + b = guit->bitmap->create(size, size, BITMAP_OPAQUE); if (b == NULL) return NULL; @@ -4941,58 +5090,68 @@ treeview_generate_triangle_bitmap(colour bg, colour fg, int size) if (y < size / 2) { /* Top half */ for (x = 0; x < y * 2; x++) { - *(pos++) = red_from_colour(colour4); - *(pos++) = green_from_colour(colour4); - *(pos++) = blue_from_colour(colour4); - *(pos++) = 0xff; + pos[bitmap_layout.r] = red_from_colour(colour4); + pos[bitmap_layout.g] = green_from_colour(colour4); + pos[bitmap_layout.b] = blue_from_colour(colour4); + pos[bitmap_layout.a] = 0xff; + pos += 4; } - *(pos++) = red_from_colour(colour3); - *(pos++) = green_from_colour(colour3); - *(pos++) = blue_from_colour(colour3); - *(pos++) = 0xff; - *(pos++) = red_from_colour(colour1); - *(pos++) = green_from_colour(colour1); - *(pos++) = blue_from_colour(colour1); - *(pos++) = 0xff; + pos[bitmap_layout.r] = red_from_colour(colour3); + pos[bitmap_layout.g] = green_from_colour(colour3); + pos[bitmap_layout.b] = blue_from_colour(colour3); + pos[bitmap_layout.a] = 0xff; + pos += 4; + pos[bitmap_layout.r] = red_from_colour(colour1); + pos[bitmap_layout.g] = green_from_colour(colour1); + pos[bitmap_layout.b] = blue_from_colour(colour1); + pos[bitmap_layout.a] = 0xff; + pos += 4; for (x = y * 2 + 2; x < size ; x++) { - *(pos++) = red_from_colour(colour0); - *(pos++) = green_from_colour(colour0); - *(pos++) = blue_from_colour(colour0); - *(pos++) = 0xff; + pos[bitmap_layout.r] = red_from_colour(colour0); + pos[bitmap_layout.g] = green_from_colour(colour0); + pos[bitmap_layout.b] = blue_from_colour(colour0); + pos[bitmap_layout.a] = 0xff; + pos += 4; } } else if ((y == size / 2) && (size & 0x1)) { /* Middle row */ for (x = 0; x < size - 1; x++) { - *(pos++) = red_from_colour(colour4); - *(pos++) = green_from_colour(colour4); - *(pos++) = blue_from_colour(colour4); - *(pos++) = 0xff; + pos[bitmap_layout.r] = red_from_colour(colour4); + pos[bitmap_layout.g] = green_from_colour(colour4); + pos[bitmap_layout.b] = blue_from_colour(colour4); + pos[bitmap_layout.a] = 0xff; + pos += 4; } - *(pos++) = red_from_colour(colour2); - *(pos++) = green_from_colour(colour2); - *(pos++) = blue_from_colour(colour2); - *(pos++) = 0xff; + pos[bitmap_layout.r] = red_from_colour(colour2); + pos[bitmap_layout.g] = green_from_colour(colour2); + pos[bitmap_layout.b] = blue_from_colour(colour2); + pos[bitmap_layout.a] = 0xff; + pos += 4; } else { /* Bottom half */ for (x = 0; x < (size - y - 1) * 2; x++) { - *(pos++) = red_from_colour(colour4); - *(pos++) = green_from_colour(colour4); - *(pos++) = blue_from_colour(colour4); - *(pos++) = 0xff; + pos[bitmap_layout.r] = red_from_colour(colour4); + pos[bitmap_layout.g] = green_from_colour(colour4); + pos[bitmap_layout.b] = blue_from_colour(colour4); + pos[bitmap_layout.a] = 0xff; + pos += 4; } - *(pos++) = red_from_colour(colour3); - *(pos++) = green_from_colour(colour3); - *(pos++) = blue_from_colour(colour3); - *(pos++) = 0xff; - *(pos++) = red_from_colour(colour1); - *(pos++) = green_from_colour(colour1); - *(pos++) = blue_from_colour(colour1); - *(pos++) = 0xff; + pos[bitmap_layout.r] = red_from_colour(colour3); + pos[bitmap_layout.g] = green_from_colour(colour3); + pos[bitmap_layout.b] = blue_from_colour(colour3); + pos[bitmap_layout.a] = 0xff; + pos += 4; + pos[bitmap_layout.r] = red_from_colour(colour1); + pos[bitmap_layout.g] = green_from_colour(colour1); + pos[bitmap_layout.b] = blue_from_colour(colour1); + pos[bitmap_layout.a] = 0xff; + pos += 4; for (x = (size - y) * 2; x < size ; x++) { - *(pos++) = red_from_colour(colour0); - *(pos++) = green_from_colour(colour0); - *(pos++) = blue_from_colour(colour0); - *(pos++) = 0xff; + pos[bitmap_layout.r] = red_from_colour(colour0); + pos[bitmap_layout.g] = green_from_colour(colour0); + pos[bitmap_layout.b] = blue_from_colour(colour0); + pos[bitmap_layout.a] = 0xff; + pos += 4; } } @@ -5026,7 +5185,7 @@ treeview_generate_copy_bitmap(struct bitmap *orig, int size) assert(size == guit->bitmap->get_height(orig)); /* Create the bitmap */ - b = guit->bitmap->create(size, size, BITMAP_NEW | BITMAP_OPAQUE); + b = guit->bitmap->create(size, size, BITMAP_OPAQUE); if (b == NULL) return NULL; @@ -5074,7 +5233,7 @@ treeview_generate_rotate_bitmap(struct bitmap *orig, int size) assert(size == guit->bitmap->get_height(orig)); /* Create the bitmap */ - b = guit->bitmap->create(size, size, BITMAP_NEW | BITMAP_OPAQUE); + b = guit->bitmap->create(size, size, BITMAP_OPAQUE); if (b == NULL) return NULL; @@ -5195,7 +5354,7 @@ nserror treeview_init(void) 10 + 36) / 72; tree_g.line_height = (font_px_size * 8 + 3) / 6; - res = treeview_init_plot_styles(font_pt_size * FONT_SIZE_SCALE / 10); + res = treeview_init_plot_styles(font_pt_size * PLOT_STYLE_SCALE / 10); if (res != NSERROR_OK) { return res; } diff --git a/desktop/treeview.h b/desktop/treeview.h index a8cf29ac5..2cf6902a8 100644 --- a/desktop/treeview.h +++ b/desktop/treeview.h @@ -32,7 +32,6 @@ struct redraw_context; struct core_window; -struct core_window_callback_table; typedef struct treeview treeview; typedef struct treeview_node treeview_node; @@ -189,9 +188,10 @@ nserror treeview_fini(void); */ nserror treeview_create(treeview **tree, const struct treeview_callback_table *callbacks, - int n_fields, struct treeview_field_desc fields[], - const struct core_window_callback_table *cw_t, - struct core_window *cw, treeview_flags flags); + int n_fields, + struct treeview_field_desc fields[], + struct core_window *cw, + treeview_flags flags); /** @@ -204,9 +204,7 @@ nserror treeview_create(treeview **tree, * \param cw The core_window in which the treeview is shown * \return NSERROR_OK on success, appropriate error otherwise */ -nserror treeview_cw_attach(treeview *tree, - const struct core_window_callback_table *cw_t, - struct core_window *cw); +nserror treeview_cw_attach(treeview *tree, struct core_window *cw); /** @@ -495,4 +493,15 @@ void treeview_edit_selection(treeview *tree); */ int treeview_get_height(treeview *tree); + +/** + * Set the search string for a treeview with \ref TREEVIEW_SEARCHABLE + * + * \param tree Tree to set the search string for. + * \return NSERROR_OK on success, appropriate error otherwise + */ +nserror treeview_set_search_string( + treeview *tree, + const char *string); + #endif diff --git a/desktop/version.c b/desktop/version.c index bbe759f2e..91a7532f4 100644 --- a/desktop/version.c +++ b/desktop/version.c @@ -1,5 +1,5 @@ /* - * Copyright 2016 Vincent Sanders <vince@netsurf-browser.org> + * Copyright 2019 Vincent Sanders <vince@netsurf-browser.org> * * This file is part of NetSurf, http://www.netsurf-browser.org/ * @@ -20,11 +20,11 @@ #include "desktop/version.h" -const char * const netsurf_version = "3.8 (Dev" +const char * const netsurf_version = "3.12 (Dev" #if defined(CI_BUILD) " CI #" CI_BUILD #endif ")" ; const int netsurf_version_major = 3; -const int netsurf_version_minor = 8; +const int netsurf_version_minor = 12; |